【问题标题】:Mocking my own service in a unit test在单元测试中模拟我自己的服务
【发布时间】:2012-11-15 13:08:49
【问题描述】:

我有一个服务将我的其他几个服务作为依赖项。如何模拟它进行单元测试?

myApp.factory('serviceToTest',
           ['serviceDependency',
    function(serviceDependency) {
        return function(args) {
            return cond(args) ? serviceDependency() : somethingElse();
        };
    }
]);

在上面的示例中,我想模拟出serviceDependency,以便验证它是否被调用。我该怎么做?

我可以在测试中执行以下操作:

describe("Services", function() {
    describe('serviceToTest', function() {

        myApp.factory('serviceDependency', function() {
            var timesCalled = 0;
            return function() {
                return timesCalled++;
            }
        });

        it('should do foo', inject(function(serviceToTest, serviceDependency) {
            serviceToTest(["foo", "bar", "baz"]);
            expect(serviceDependency()).to.equal(1);
        });
    });
});

这对于需要模拟的测试很有效,但它会影响随后所有其他测试的状态,这显然是一个问题。

【问题讨论】:

  • 在您的测试中,您如何获得 serviceToTest 的实例?

标签: unit-testing mocking angularjs


【解决方案1】:

这是我的开源项目中的一个示例:https://github.com/lucassus/mongo_browser/blob/f1faf1b89a9fc33ef4bc4eced386c30bda029efa/spec/javascripts/app/services_spec.js.coffee#L25(对于咖啡脚本感到抱歉)。 通常,您必须在规范中创建并包含一个覆盖给定服务的新模块。

【讨论】:

    【解决方案2】:

    如果我对您的理解正确,您想测试一个依赖于另一个服务的服务并模拟每个测试的依赖项。如果是这样,假设我们有一个依赖于enginecar

    var app = angular.module('plunker', [])
      .factory('car', function(engine) {
         return {
           drive : function() {
             return 'Driving: ' + engine.speed();
           }
         }
      })
      .value('engine', {
        speed : function() {
          return 'fast';
        }
      });
    

    然后您想测试汽车并模拟引擎。有两种方法:要么定义一个新模块,我们可以在其中重新定义一个引擎:

    describe('Testing a car', function() {
      var testEngine;
    
      beforeEach(function(){
        testEngine = {};
        angular.module('test', ['plunker']).value('engine', testEngine);
        module('test');
      });   
    
      it('should drive slow with a slow engine', inject(function(car) {
        testEngine.speed = function() {
          return 'slow';
        };
        expect(car.drive()).toEqual('Driving: slow');
      }));
    });
    

    这里的工作人员:http://plnkr.co/edit/ueXIzk?p=preview

    一个更简单的替代方案,基于 JavaScript 的动态特性:

    describe('Testing a car', function() {
      var testEngine;
    
      beforeEach(module('plunker'));
      beforeEach(inject(function(engine){
        testEngine = engine;
      }));
    
      it('should drive slow with a slow engine', inject(function(car) {
        testEngine.speed = function() {
          return 'slow';
        };
        expect(car.drive()).toEqual('Driving: slow');
      }));
    });
    

    http://plnkr.co/edit/tlHnsJ?p=preview

    另一种选择是使用 Jasmine 的间谍:

    describe('Testing a car', function() {
      var testEngine;
    
      beforeEach(module('plunker'));
      beforeEach(inject(function(engine){
        testEngine = engine;
      }));
    
      it('should drive slow with a slow engine', inject(function(car) {
        spyOn(testEngine, 'speed').andReturn('slow');
        expect(car.drive()).toEqual('Driving: slow');
      }));
    });
    

    http://plnkr.co/edit/K4jczI?p=preview

    【讨论】:

    • 您能否解释一下在我根本无法实际加载“引擎”依赖项的情况下如何操作。在那种情况下,我不能做 testEngine = engine;部分。
    • @KamalReddy 晚了 2 年,但为了后代:只需使用上面的第一个解决方案。如果您定义了一个全新的“testEngine”对象来覆盖原始依赖项的定义,那么您是否可以加载“引擎”并不重要。
    【解决方案3】:

    我在尝试将模拟服务注入另一个提供商时遇到了同样的问题。

    Pete Bacon Darwin 的这篇文章让我走上了正轨:https://groups.google.com/d/msg/angular/TvBknXnjRyQ/xtCDkJyqp6MJ

    这是一个示例应用程序:

    angular.module('MyApplication', [])
        .service('MyServiceDependency', [function(){
            // Behaviour here.
        }])
        .factory('MyFactory', ['MyServiceDependency', function(MyServiceDependency){
            // Behaviour here.
        }]);
    

    我们想模拟 MyServiceDependency 来测试 MyFactory 的行为,所以我们这样编写测试:

    describe(function(){
    
        var MyFactory;
    
        beforeEach(function(){
            // 1. Include your application module for testing.
            angular.mock.module('MyApplication');
    
            // 2. Define a new module.
            // 3. Define a provider with the same name as the one you want to mock (in our case we want to mock 'MyServiceDependency'.
            angular.module('MyAppMock', [])
                .service('MyServiceDependency', function(){
                    // Define you mock behaviour here.
                });
    
            // 4. Include your new mock module - this will override the providers from your original module.
            angular.mock.module('MyAppMock');
    
            // 5. Get an instance of the provider you want to test.
            inject(function($injector){
                // `MyFactory` will receive the mocked version of `MyServiceDependency`.
                MyFactory = $injector.get('MyFactory');
            });
        });
    
        it('MyFactory does something special', function(){
            MyFactory(); // Test your provider.
        });
    
    });
    

    【讨论】:

    • 美丽 - 我怀疑我自己会想出来。恕我直言,这是最灵活的方法,因为您现在可以在单元测试之外定义一组自定义模拟。然后,您可以在单元测试中指向与设置禁用外部依赖项(例如对 $http 的调用)的运行环境时相同的模拟。
    猜你喜欢
    • 1970-01-01
    • 2020-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-18
    • 2021-04-05
    相关资源
    最近更新 更多