How to mock an object within an AngularJS factory

2019-08-15 05:01发布

I have created an Angular factory that has methods which handle saving code to a server. One of the factory methods contains a third party object which has a method which does the actual callout. I would like to test this code, but I can't work out how to mock out the third party object.

I have set up a plunker with a Jasmine test.

My aim for this test is just to successfully get the code to use my mock object rather than the ThirdPartySavingUtils object. Is that possible?

var app = angular.module("MyApp", []);

app.factory("SavingUtils", function() {

  return {

    saveStuff: function() {
      if(typeof ThirdPartySavingUtils !== "undefined") {
        return ThirdPartySavingUtils.value;
      }
    }
  };
});

this is my jasmine tests

describe("Mocking Test", function() {

  var ThirdPartySavingUtilsMock;
  var SavingUtils;

  beforeEach(function() {

    angular.mock.module("MyApp", function($provide) {

      ThirdPartySavingUtilsMock = {
        value: "I am the mock object"
      };

      $provide.value("ThirdPartySavingUtils", ThirdPartySavingUtilsMock);
    });

    inject(function(_SavingUtils_) {
      SavingUtils = _SavingUtils_;
    });
  });

  it("should run without throwing an exception", function() {

    expect(true).toBe(true);
  });

  it("should mock out ThirdPartySavingUtils with ThirdPartySavingUtilsMock", function() {

    var result = SavingUtils.saveStuff();

    expect(result).toEqual("I am the mock object");
  });
});

1条回答
我欲成王,谁敢阻挡
2楼-- · 2019-08-15 05:59

You have a few options really but more than likely you would need to do both.

1) You could create an angular service which wraps this third party object - this way you get a nice abstraction incase you ever need to change the third party object.

2) You could use a mocking framework like http://sinonjs.org/ which enable you to mock methods out and do asserts like calledOnce etc.

Here is a link to a mocked test using sinon test.

You can bascially see sinon is used as a sandbox to mock out an object methods. Sinon provides extra propeties to those mocked methods so you can assert if they were called, the parameters they were called with even the order of the calls. It is a really, really essential testing tool.

describe('validationManager', function () {
        beforeEach(inject(function ($injector) {
            sandbox = sinon.sandbox.create();
            $rootScope = $injector.get('$rootScope');
            $compile = $injector.get('$compile');
            $q = $injector.get('$q');
            defer = $q.defer();
            validator = $injector.get('validator');
            validationManager = $injector.get('validationManager');

            sandbox.stub(validator, 'makeValid');
            sandbox.stub(validator, 'makeInvalid');
            sandbox.stub(validator, 'getErrorMessage').returns(defer.promise);

            setModelCtrl();
        }));

        afterEach(function () {
            sandbox.restore();
            setModelCtrl();
        });

        it('should be defined', function () {
            expect(validationManager).to.exist;
        });

        describe('validateElement', function () {
            it('should return if no $parsers or $formatters on the controller', function () {
                validationManager.validateElement(modelCtrl);
                expect(validator.makeValid.called).to.equal(false);
                expect(validator.makeInvalid.called).to.equal(false);
            });

});

EDIT -----------------------

Here this put into practice for your code (I haven't run this but it give the general idea).

(function (angular, ThirdPartyApi) {
    'use strict';

    var app = angular.module('MyApp', []);

    app.factory('thirdPartApi', [
        function () {
            return {
                save: ThirdPartyApi.save,
                value: ThirdPartyApi.value
            };
        }
    ]);

    app.factory('SavingUtils', [
        'thirdPartApi',
        function (thirdPartApi) {
            var getValue = function () {
                    return thirdPartApi.value;
                },
                save = function (item) {
                    return thirdPartApi.save(item);
                };

            return {
                save: save,
                getValue: getValue
            };
        }
    ]);
}(angular, window.ThirdPartyApi));

The tests.....

(function (angular, sinon) {
    'use strict';

    describe('MyApp.SavingUtils', function () {
       var sandbox, thirdPartyApi, SavingUtils, thirdPartyApiValue = 2;

        beforeEach(inject(function ($injector) {
            sandbox = sinon.sandbox.create();
            thirdPartyApi = $injector.get('thirdPartyApi');
            SavingUtils = $injector.get('SavingUtils');

            // stub the method and when called return a simple object or whatever you want
            sandbox.stub(thirdPartyApi, 'save').returns({ id: 1});
            sandbox.stub(thirdPartyApi, 'value', function () {
                return thirdPartyApiValue;
            });
        }));

        afterEach(function () {
            // This removes those stubs and replace the original methods/values
            sandbox.restore();
        });

        describe('save', function () {
            it('should return call the save method on thirdPartyApi', function () {
                var item = {};
                SavingUtils.save(item);

                expect(thirdPartyApi.save.calledOnce).to.equal(true);
            });
        });

        describe('getValue', function () {
            it('should return value of value property on thirdPartyApi', function () {
                var result = SavingUtils.getValue();

                expect(result).to.equal(thirdPartyApiValue);
            });
        });
    });
}(angular, sinon));
查看更多
登录 后发表回答