0

I am trying to write units test for my app and I have the following issue

In my controller, I have something like

 $scope.test1 = function() {
     productFactory.getName()
         .then(function(products){
             $scope.result = products;
          })
} 

productFactory

angular.module('myApp').factory('productFactory', function($http) {
    var factoryObj = {};
    factoryObj.getName = function() {
        return http.get(url)
    }

    return factoryObj
})

In my unit test file

describe('test here', function () {
    var testCtrl, scope, httpBackend, mockFactory;

    beforeEach(module('myApp', function($provide){
        $provide.value('productFactory', mockFactory);
    }));

    // Initialize the controller and a mock scope
    beforeEach(inject(function (_$controller_, _$rootScope_, _$httpBackend_,  _productFactory_) {
        scope = _$rootScope_.$new();
        httpBackend = _$httpBackend_;
        mockFactory = _productFactory_;

        testCtrl = _$controller_('testCtrl', {
            $scope: scope
        });

    it('should get product name', function() {       
        scope.test1();
        //I am not sure how to test the results
    });
}));

When I run karma test, it gives me

TypeError: 'undefined' is not an object (evaluating 'productFactory.getName()')

I am not sure how to test the http result and fix the error. Can anyone help me about it? Thanks a lot!

1
  • Post the remaining code for the controller, it looks like you aren't injecting productFactory into the controller. Commented Feb 5, 2015 at 23:49

1 Answer 1

1

First of all, you don't need to worry about using $provide:

beforeEach(module('myApp'));

1. Without $httpBackend (mock out the service completely)

Then, productFactory will be passed into your controller, but you want to spyOn the getName():

// Initialize the controller and a mock scope
beforeEach(inject(function (_$controller_, _$rootScope_, _$httpBackend_,  _productFactory_) {
    scope = _$rootScope_.$new();
    httpBackend = _$httpBackend_;
    mockFactory = _productFactory_;

    // add spy for the method, wrap with $q.when so it returns a promise
    spyOn(mockFactory, 'getName').and.returnValue($q.when('Pizza!'));

    testCtrl = _$controller_('testCtrl', {
        $scope: scope,
        productFactory: mockFactory  // pass in here
    });

Then, you've got to cause a $digest cycle, so that the promise will call through:

it('should get product name', function() {       
    scope.test1();

    // hit the $digest        
    scope.$apply();

    // expectation
    expect(scope.result).toBe('Pizza!')
});

2. With $httpBackend

// Initialize the controller and a mock scope
    beforeEach(inject(function (_$controller_, _$rootScope_, _$httpBackend_) {
        scope = _$rootScope_.$new();
        httpBackend = _$httpBackend_;

        // set up httpBackent
        httpBackend.when('GET', '/products')
                            .respond([{ name: 'Pizza!'}, {name: 'Sandwich'}]);

        testCtrl = _$controller_('testCtrl', {
            $scope: scope
        });

We don't need to mock the factory in this case at all. Then, we just need to flush $httpBackend when we want the http call to return:

it('should get product name', function() {       
    scope.test1();

    // hit the $digest with flush        
    httpBackend.flush();

    // expectation
    expect(scope.result.length).toBe(2)
});
Sign up to request clarification or add additional context in comments.

4 Comments

It seems like this sort of thing would be better managed via $httpBackend expectations (e.g. $httpBackend.expectGet(...).respond(200, 'Pizza!')) and then flushing. As it is, (and if I read this properly), your spy manages to hide a bug in OP's original code (using http when they really meant $http)
@mgilson, Yes, you could be right. $httpBackend might be better. I've shown how to mock the service out completely from the controller. If I was writing this app, I'd probably have tests that do both.
Thanks Davin it's very helpful. Would you please demo on how to write httpbackend as well? +1 Also, why don't I need $provide. I thought it's required to add factory?
@BonJon, @mgilson: example with $httpBackend added.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.