【问题标题】:How to test AngularJS custom provider如何测试 AngularJS 自定义提供程序
【发布时间】:2013-01-24 04:10:39
【问题描述】:

有没有人提供如何对提供程序进行单元测试的示例?

例如:

config.js

angular.module('app.config', [])
  .provider('config', function () {
    var config = {
          mode: 'distributed',
          api:  'path/to/api'
        };

    this.mode = function (type) {
      if (type) {
        config.isDistributedInstance = type === config.mode;
        config.isLocalInstance = !config.isDistributedInstance;
        config.mode = type;
        return this;
      } else {
        return config.mode;
      }
    };

    this.$get = function () {
      return config;
    };
  }]);

app.js

angular.module('app', ['app.config'])
  .config(['configProvider', function (configProvider) {
    configProvider.mode('local');
  }]);

app.js 正在测试中使用,我看到已经配置了configProvider,我可以将其作为服务进行测试。但是如何测试配置能力呢?还是根本不需要?

【问题讨论】:

    标签: unit-testing angularjs jasmine


    【解决方案1】:

    我也有同样的问题,只在Google Group answer 中找到了一个可行的解决方案,它被引用为fiddle example

    测试您的提供程序代码将如下所示(遵循fiddle example 中的代码以及对我有用的代码):

    describe('Test app.config provider', function () {
    
        var theConfigProvider;
    
        beforeEach(function () {
            // Initialize the service provider 
            // by injecting it to a fake module's config block
            var fakeModule = angular.module('test.app.config', function () {});
            fakeModule.config( function (configProvider) {
                theConfigProvider = configProvider;
            });
            // Initialize test.app injector
            module('app.config', 'test.app.config');
    
            // Kickstart the injectors previously registered 
            // with calls to angular.mock.module
            inject(function () {});
        });
    
        describe('with custom configuration', function () {
            it('tests the providers internal function', function () {
                // check sanity
                expect(theConfigProvider).not.toBeUndefined();
                // configure the provider
                theConfigProvider.mode('local');
                // test an instance of the provider for 
                // the custom configuration changes
                expect(theConfigProvider.$get().mode).toBe('local');
            });
        });
    
    });
    

    【讨论】:

    • 感谢您的这篇文章!我按照这里找到的指南失败了:github.com/angular/angular.js/issues/2274。上面的示例按预期工作。谢谢!
    • 执行此操作时,我必须更改假模块的声明以传入空数组而不是空函数。可能是由于更新版本的 Angular 造成的。
    【解决方案2】:

    我一直在使用@Mark Gemmill 的解决方案,它运行良好,但后来偶然发现这个稍微不那么冗长的解决方案,它消除了对假模块的需求。

    https://stackoverflow.com/a/15828369/1798234

    所以,

    var provider;
    
    beforeEach(module('app.config', function(theConfigProvider) {
        provider = theConfigProvider;
    }))
    
    it('tests the providers internal function', inject(function() {
        provider.mode('local')
        expect(provider.$get().mode).toBe('local');
    }));
    


    如果你的 providers $get 方法有依赖,你可以手动传入,

    var provider;
    
    beforeEach(module('app.config', function(theConfigProvider) {
        provider = theConfigProvider;
    }))
    
    it('tests the providers internal function', inject(function(dependency1, dependency2) {
        provider.mode('local')
        expect(provider.$get(dependency1, dependency2).mode).toBe('local');
    }));
    


    或者使用 $injector 创建一个新实例,

    var provider;
    
    beforeEach(module('app.config', function(theConfigProvider) {
        provider = theConfigProvider;
    }))
    
    it('tests the providers internal function', inject(function($injector) {
        provider.mode('local')
        var service = $injector.invoke(provider);
        expect(service.mode).toBe('local');
    }));
    


    以上两种方法还允许您为describe 块中的每个it 语句重新配置提供程序。但是如果你只需要为多个测试配置一次提供程序,你可以这样做,

    var service;
    
    beforeEach(module('app.config', function(theConfigProvider) {
        var provider = theConfigProvider;
        provider.mode('local');
    }))
    
    beforeEach(inject(function(theConfig){
        service = theConfig;
    }));
    
    it('tests the providers internal function', function() {
        expect(service.mode).toBe('local');
    });
    
    it('tests something else on service', function() {
        ...
    });
    

    【讨论】:

      【解决方案3】:

      @Stephane Catala 的回答特别有帮助,我使用他的 providerGetter 来得到我想要的东西。能够让提供者进行初始化,然后通过实际服务来验证各种设置是否正常工作,这一点很重要。示例代码:

          angular
              .module('test', [])
              .provider('info', info);
      
          function info() {
              var nfo = 'nothing';
              this.setInfo = function setInfo(s) { nfo = s; };
              this.$get = Info;
      
              function Info() {
                  return { getInfo: function() {return nfo;} };
              }
          }
      

      Jasmine 测试规范:

          describe("provider test", function() {
      
              var infoProvider, info;
      
              function providerGetter(moduleName, providerName) {
                  var provider;
                  module(moduleName, 
                               [providerName, function(Provider) { provider = Provider; }]);
                  return function() { inject(); return provider; }; // inject calls the above
              }
      
              beforeEach(function() {
                  infoProvider = providerGetter('test', 'infoProvider')();
              });
      
              it('should return nothing if not set', function() {
                  inject(function(_info_) { info = _info_; });
                  expect(info.getInfo()).toEqual('nothing');
              });
      
              it('should return the info that was set', function() {
                  infoProvider.setInfo('something');
                  inject(function(_info_) { info = _info_; });
                  expect(info.getInfo()).toEqual('something');
              });
      
          });
      

      【讨论】:

      • 其他人可以随时删除他们的答案。因此,您的答案不应提及任何其他答案。
      • 只尝试在信用到期时给予信用。没有前一个,我的答案是完整的,尽管它确实对 providerGetter 进行了更深入的研究——但我花了一段时间才弄清楚如何测试提供者阶段和服务阶段(这对我的实际情况很重要)所以以为我会把它扔在这里——似乎没有其他答案可以同时测试设置和使用。
      • 这很好,但信用会归于信用。在这种情况下,如果您有权放弃投票,那么信用的地方就是放弃投票。否则,等待机会放弃投票。您的答案可能会更好,但这不是我的主题(因为我在这里参与审查您的问题),也不是将功劳归于其他答案的正确地方。有关更多信息,请加入 meta SO 并了解有关社区标准的更多信息。 @ScottG
      • 也许您可以编辑和扩展您所信任的答案,而不是回答和归功于(我很佩服)。
      【解决方案4】:

      这里有一个小助手,可以正确封装获取提供程序,从而确保各个测试之间的隔离:

        /**
         * @description request a provider by name.
         *   IMPORTANT NOTE: 
         *   1) this function must be called before any calls to 'inject',
         *   because it itself calls 'module'.
         *   2) the returned function must be called after any calls to 'module',
         *   because it itself calls 'inject'.
         * @param {string} moduleName
         * @param {string} providerName
         * @returns {function} that returns the requested provider by calling 'inject'
         * usage examples:
          it('fetches a Provider in a "module" step and an "inject" step', 
              function() {
            // 'module' step, no calls to 'inject' before this
            var getProvider = 
              providerGetter('module.containing.provider', 'RequestedProvider');
            // 'inject' step, no calls to 'module' after this
            var requestedProvider = getProvider();
            // done!
            expect(requestedProvider.$get).toBeDefined();
          });
         * 
          it('also fetches a Provider in a single step', function() {
            var requestedProvider = 
              providerGetter('module.containing.provider', 'RequestedProvider')();
      
            expect(requestedProvider.$get).toBeDefined();
          });
         */
        function providerGetter(moduleName, providerName) {
          var provider;
          module(moduleName, 
                 [providerName, function(Provider) { provider = Provider; }]);
          return function() { inject(); return provider; }; // inject calls the above
        }
      
      • 获取提供程序的过程被完全封装:不需要减少测试之间隔离的闭包变量。
      • 该过程可以分为两个步骤,一个“模块”步骤和一个“注入”步骤,可以分别与单元测试中对“模块”和“注入”的其他调用进行分组。
      • 如果不需要拆分,只需一个命令即可检索提供程序!

      【讨论】:

        【解决方案5】:

        我个人使用这种技术来模拟来自外部库的提供程序,您可以将它们放入一个帮助文件中以进行所有测试。当然,它也可以用于自定义提供程序,就像在这个问题中一样。这个想法是在应用程序调用它之前在他的模块中重新定义提供程序

        describe('app', function() {
          beforeEach(module('app.config', function($provide) {
            $provide.provider('config', function() {
              var mode = jasmine.createSpy('config.mode');
        
              this.mode = mode;
        
              this.$get = function() {
                return {
                  mode: mode
                };
              };
            });
          }));
        
          beforeEach(module('app'));
        
          describe('.config', function() {
            it('should call config.mode', inject(function(config) {
              expect(config.mode).toHaveBeenCalled();
            }));
          });
        });
        

        【讨论】:

          【解决方案6】:

          我只需要测试提供程序上的某些设置是否正确设置,因此我在通过 module() 初始化模块时使用 Angular DI 配置提供程序。

          在尝试了上述一些解决方案后,我还遇到了一些关于未找到提供程序的问题,因此强调需要一种替代方法。

          之后,我添加了进一步的测试,使用这些设置来检查它们是否反映了新设置值的使用。

          describe("Service: My Service Provider", function () {
              var myService,
                  DEFAULT_SETTING = 100,
                  NEW_DEFAULT_SETTING = 500;
          
              beforeEach(function () {
          
                  function configurationFn(myServiceProvider) {
                      /* In this case, `myServiceProvider.defaultSetting` is an ES5 
                       * property with only a getter. I have functions to explicitly 
                       * set the property values.
                       */
                      expect(myServiceProvider.defaultSetting).to.equal(DEFAULT_SETTING);
          
                      myServiceProvider.setDefaultSetting(NEW_DEFAULT_SETTING);
          
                      expect(myServiceProvider.defaultSetting).to.equal(NEW_DEFAULT_SETTING);
                  }
          
                  module("app", [
                      "app.MyServiceProvider",
                      configurationFn
                  ]);
          
                  function injectionFn(_myService) {
                      myService = _myService;
                  }
          
                  inject(["app.MyService", injectionFn]);
              });
          
              describe("#getMyDefaultSetting", function () {
          
                  it("should test the new setting", function () {
                      var result = myService.getMyDefaultSetting();
          
                       expect(result).to.equal(NEW_DEFAULT_SETTING);
                  });
          
              });
          
          });
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-03-05
            • 1970-01-01
            • 1970-01-01
            • 2022-01-01
            相关资源
            最近更新 更多