Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
148 views
in Technique[技术] by (71.8m points)

javascript - Unit testing in AngularJS - Mocking Services and Promises

In Angular everything seems to have a steep learning curve and unit testing an Angular app definitely doesn't escape this paradigm.

When I started with TDD and Angular I felt that I was spending twice (maybe more) as much time figuring out just how to test and maybe even more just getting my tests set up correctly. But as Ben Nadel put it in his blog there are ups and downs in the angular learning process. His graph is definitely my experience with Angular.

However as I have progressed in learning Angular and unit testing as well, now i feel that I am spending much less time setting up tests and much more time making tests go from red to green - which is a good feeling.

So I have come across different methods of setting up my unit test to mock services and promises and I thought I would share what I have learned and also ask the question of:

Are there any other or better ways of accomplishing this?

So onto the code, that what we all come for here anyways - not to listen to some guy talk about his love, err accomplishments learning a framework.

This is how I started out mocking my services and promises, I'll use a controller, but services and promises can be mocked in other places obviously.

describe('Controller: Products', function () {
    var//iable declarations
        $scope,
        $rootScope,
        ProductsMock = {
            getProducts: function () {
            } // There might be other methods as well but I'll stick to one for the sake of consiseness
        },
        PRODUCTS = [{},{},{}]
    ;

    beforeEach(function () {
        module('App.Controllers.Products');
    });

    beforeEach(inject(function ($controller, _$rootScope_) {
        //Set up our mocked promise
        var promise = { then: jasmine.createSpy() };

        //Set up our scope
        $rootScope = _$rootScope_;
        $scope = $rootScope.$new();

        //Set up our spies
        spyOn(ProductsMock, 'getProducts').andReturn(promise);

        //Initialize the controller
        $controller('ProductsController', {
            $scope: $scope,
            Products: ProductsMock
        });

        //Resolve the promise
        promise.then.mostRecentCall.args[0](PRODUCTS);

    }));

    describe('Some Functionality', function () {
        it('should do some stuff', function () {
            expect('Stuff to happen');
        });
    });
});

For us this worked, but as time went on I thought there must be a better way. For one I hated the

promise.then.mostRecentCall 

thing, and if we wanted to reinitialise the controller then we had to pull it out of the beforeEach block and inject it individually into each test.

There has to be a better way...

Now I ask does anyone have other ways to set tests up, or and thoughts or feeling on the way I have chose to do it?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Then I came across another post, blog, stackoverflow example (you pick it I was probably there), and I saw the use of the $q library. Duh! Why set up a whole mock promise when we can just use the tool that Angular gives us. Our code looks nicer and makes more sense to look at - no ugly promise.then.mostRecent thing.

Next in the iteration of unit testing was this:

describe('Controller: Products', function () {
    var//iable declarations
        $scope,
        $rootScope,
        $q,
        $controller,
        productService,
        PROMISE = {
            resolve: true,
            reject: false
        },
        PRODUCTS = [{},{},{}] //constant for the products that are returned by the service
    ;

    beforeEach(function () {
        module('App.Controllers.Products');
        module('App.Services.Products');
    });


    beforeEach(inject(function (_$controller_, _$rootScope_, _$q_, _products_) {
        $rootScope = _$rootScope_;
        $q = _$q_;
        $controller = _$controller_;
        productService = _products_;
        $scope = $rootScope.$new();
    }));

    function setupController(product, resolve) {
        //Need a function so we can setup different instances of the controller
        var getProducts = $q.defer();

        //Set up our spies
        spyOn(products, 'getProducts').andReturn(getProducts.promise);

        //Initialise the controller
        $controller('ProductsController', {
            $scope: $scope,
            products: productService
        });

        // Use $scope.$apply() to get the promise to resolve on nextTick().
        // Angular only resolves promises following a digest cycle,
        // so we manually fire one off to get the promise to resolve.
        if(resolve) {
            $scope.$apply(function() {
                getProducts.resolve();
            });
        } else {
            $scope.$apply(function() {
                getProducts.reject();
            });
        }
    }

    describe('Resolving and Rejecting the Promise', function () {
        it('should return the first PRODUCT when the promise is resolved', function () {
            setupController(PRODUCTS[0], PROMISE.resolve); // Set up our controller to return the first product and resolve the promise. 
            expect('to return the first PRODUCT when the promise is resolved');
        });

        it('should return nothing when the promise is rejected', function () {
            setupController(PRODUCTS[0], PROMISE.reject); // Set up our controller to return first product, but not to resolve the promise. 
            expect('to return nothing when the promise is rejected');
        });
    });
});

This started to feel like the way it should be set up. We can mock what we need to mock we can set our promise to resolve and reject so we can truly test the two possible outcomes. This feels good...


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...