是否可以在测试中覆盖模块配置函数的常量?

时间:2023-01-15 21:50:43

I've spent quite a while banging my head against trying to override injected constants provided to modules' config functions. My code looks something like

我花了很长一段时间试图重写为模块配置函数提供的注入常量。我的代码看起来是这样的

common.constant('I18n', <provided by server, comes up as undefined in tests>);
common.config(['I18n', function(I18n) {
  console.log("common I18n " + I18n)
}]);

Our usual way to guarantee what I18n is being injected in our unit tests is by doing

我们保证在单元测试中注入I18n的通常方法是做

module(function($provide) {
  $provide.constant('I18n', <mocks>);
});

This works fine for my controllers, but it seems like the config function doesn't look at what's $provided outside of the module. Instead of getting the mocked values, it gets the earlier value defined as part of the module. (Undefined in the case of our tests; in the below plunker, 'foo'.)

这对我的控制器来说很好,但是配置函数似乎不考虑模块外部提供的$。它不获取受模拟值,而是获取作为模块一部分定义的早期值。(在我们的测试中没有定义;在下面的柱塞中,“foo”。

A working plunker is below (look at the console); does anyone know what I'm doing wrong?

工作柱塞在下面(看控制台);有人知道我做错了什么吗?

http://plnkr.co/edit/utCuGmdRnFRUBKGqk2sD

http://plnkr.co/edit/utCuGmdRnFRUBKGqk2sD

5 个解决方案

#1


25  

First of all: it seems that jasmine is not working properly in your plunkr. But I am not quite sure – maybe someone else can check this again. Nevertheless I have created a new plunkr (http://plnkr.co/edit/MkUjSLIyWbj5A2Vy6h61?p=preview) and followed these instructions: https://github.com/searls/jasmine-all.

首先:茉莉花似乎在你的柱塞中没有正常工作。但我不太确定——也许有人可以再检查一下。尽管如此,我还是创建了一个新的plunkr (http://plnkr.co/edit/mkujsliywbj5a2vy6h61?

You will see that your beforeEach code will never run. You can check this:

您将看到,您的beforeEach代码将永远不会运行。你可以检查这个:

module(function($provide) {
  console.log('you will never see this');
  $provide.constant('I18n', { FOO: "bar"});
});

You need two things:

你需要两件事:

  1. A real test in the it function – expect(true).toBe(true) is good enough

    it函数中的一个真正的测试- expect(true).toBe(true)就足够了

  2. You must use inject somewhere in your test, otherwise the function provided to module will not be called and the constant will not be set.

    您必须在测试的某个地方使用inject,否则将不调用提供给模块的函数,也不会设置常量。

If you run this code you will see "green":

如果你运行这段代码,你会看到“绿色”:

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

common.constant('I18n', 'foo');
common.config(['I18n', function(I18n) {
  console.log("common I18n " + I18n)
}]);

var app = angular.module('plunker', ['common']);
app.config(['I18n', function(I18n) {
  console.log("plunker I18n " + I18n)
}]);

describe('tests', function() {

  beforeEach(module('common'));
  beforeEach(function() {
    module(function($provide) {
      console.log('change to bar');
      $provide.constant('I18n', 'bar');
    });
  });
  beforeEach(module('plunker'));    

  it('anything looks great', inject(function($injector) {
      var i18n = $injector.get('I18n');
      expect(i18n).toBe('bar');
  }));
});

I hope it will work as you expect!

我希望它能像你期望的那样工作!

#2


5  

I think the fundamental issue is you are defining the constants right before the config block, so each time the module is loaded, whatever mock value that may exist will be overridden. My suggestion would be to separate out the constants and config into separate modules.

我认为最基本的问题是在配置块之前定义常量,因此每次加载模块时,可能存在的任何模拟值都将被覆盖。我的建议是将常量和配置分离到单独的模块中。

#3


4  

Although it seems like you can't change which object a AngularJS constant refers to after it's been defined, you can change properties of the object itself.

虽然在定义了一个AngularJS常量之后,您似乎无法更改它引用的对象,但是您可以更改对象本身的属性。

So in your case you can inject I18n as you would any other dependency, and then alter it before your test.

所以在你的情况下,你可以像其他依赖一样注入I18n,然后在你的测试之前修改它。

var I18n;

beforeEach(inject(function (_I18n_) {
  I18n = _I18n_;
});

describe('A test that needs a different value of I18n.foo', function() {
  var originalFoo;

  beforeEach(function() {
    originalFoo = I18n.foo;
    I18n.foo = 'mock-foo';
  });

  it('should do something', function() {
    // Test that depends on different value of I18n.foo;
    expect(....);
  });

  afterEach(function() {
    I18n.foo = originalFoo;
  });
});

As above, you should save the original state of the constant, and restore it after the test, to make sure that this test doesn't interfere with any others that you might have, now, or in the future.

如上所述,您应该保存常量的初始状态,并在测试之后恢复它,以确保该测试不会干扰您现在或将来可能有的其他测试。

#4


3  

You can override a module definition. I'm just throwing this out there as one more variation.

可以重写模块定义。我只是把它作为一个变量。

angular.module('config', []).constant('x', 'NORMAL CONSTANT');

// Use or load this module when testing
angular.module('config', []).constant('x', 'TESTING CONSTANT');


angular.module('common', ['config']).config(function(x){
   // x = 'TESTING CONSTANT';
});

Redefining a module will wipe out the previously defined module, often done on accident, but in this scenario can be used to your advantage (if you feel like packaging things that way). Just remember anything else defined on that module will be wiped out too, so you'd probably want it to be a constants only module, and this may be overkill for you.

重新定义一个模块将删除先前定义的模块(通常是在意外情况下完成的),但是在这个场景中,可以使用您的优势(如果您希望以这种方式打包东西)。记住,在那个模块上定义的其他东西也会被清除,所以你可能希望它只是一个常量模块,这对你来说可能是多余的。

#5


1  

I’m going to walk through a nastier solution as a series of annotated tests. This is a solution for situations where module overwriting is not an option. That includes cases where the original constant recipe and config block belong to the same module, as well as cases where the constant is employed by a provider constructor.

我将通过一系列带注释的测试来完成一个更糟糕的解决方案。这是一种解决方案,适用于不支持模块覆盖的情况。这包括原始常量配方和配置块属于同一模块的情况,以及提供者构造函数使用常量的情况。

You can run the code inline on SO (awesome, this is new to me!)

你可以内联地运行代码(太棒了,这对我来说是全新的!)

Please note the caveats about restoring prior state after the spec. I do not recommend this approach unless you both (a) have a good understanding of the Angular module lifecycle and (b) are sure you cannot test something any other way. The three queues of modules (invoke, config, run) are not considered public API, but on the other hand they have been consistent over the history of Angular.

请注意关于在规范之后恢复先前状态的注意事项。我不推荐这种方法,除非您(a)对角度模块的生命周期有很好的理解,(b)确定您不能以任何其他方式进行测试。模块的3个队列(调用、配置、运行)不被认为是公共API,但是另一方面,它们在角度的历史上是一致的。

There may well be a better way to approach this — I’m really not sure — but this is the only way I have found to date.

也许有更好的方法来解决这个问题——我真的不确定——但这是我迄今为止发现的唯一方法。

angular
  .module('poop', [])
  .constant('foo', 1)
  .provider('bar', class BarProvider {
    constructor(foo) {
      this.foo = foo;
    }

    $get(foo) {
      return { foo };
    }
  })
  .constant('baz', {})
  .config((foo, baz) => {
    baz.foo = foo;
  });

describe('mocking constants', () => {
  describe('mocking constants: part 1 (what you can and can’t do out of the box)', () => {
    beforeEach(module('poop'));
  
    it('should work in the run phase', () => {
      module($provide => {
        $provide.constant('foo', 2);
      });

      inject(foo => {
        expect(foo).toBe(2);
      });
    });

    it('...which includes service instantiations', () => {
      module($provide => {
        $provide.constant('foo', 2);
      });

      inject(bar => {
        expect(bar.foo).toBe(2);
      });
    });

    it('should work in the config phase, technically', () => {
      module($provide => {
        $provide.constant('foo', 2);
      });

      module(foo => {
        // Code passed to ngMock module is effectively an added config block.
        expect(foo).toBe(2);
      });

      inject();
    });

    it('...but only if that config is registered afterwards!', () => {
      module($provide => {
        $provide.constant('foo', 2);
      });
  
      inject(baz => {
        // Earlier we used foo in a config block that was registered before the
        // override we just did, so it did not have the new value.
        expect(baz.foo).toBe(1);
      });
    });
  
    it('...and config phase does not include provider instantiation!', () => {
      module($provide => {
        $provide.constant('foo', 2);
      });
  
      module(barProvider => {
        expect(barProvider.foo).toBe(1);
      });
  
      inject();
    });
  });

  describe('mocking constants: part 2 (why a second module may not work)', () => {
    // We usually think of there being two lifecycle phases, 'config' and 'run'.
    // But this is an incomplete picture. There are really at least two more we
    // can speak of, ‘registration’ and ‘provider instantiations’.
    //
    // 1. Registration — the initial (usually) synchronous calls to module methods
    //    that define services. Specifically, this is the period prior to app
    //    bootstrap.
    // 2. Provider preparation — unlike the resulting services, which are only
    //    instantiated on demand, providers whose recipes are functions will all
    //    be instantiated, in registration order, before anything else happens.
    // 3. After that is when the queue of config blocks runs. When we supply
    //    functions to ngMock module, it is effectively like calling
    //    module.config() (likewise calling `inject()` is like adding a run block)
    //    so even though we can mock the constant here successfully for subsequent
    //    config blocks, it’s happening _after_ all providers are created and
    //    after any config blocks that were previously queued have already run.
    // 4. After the config queue, the runtime injector is ready and the run queue
    //    is executed in order too, so this will always get the right mocks. In
    //    this phase (and onward) services are instantiated on demand, so $get
    //    methods (which includes factory and service recipes) will get the right
    //    mock too, as will module.decorator() interceptors.
  
    // So how do we mock a value before previously registered config? Or for that
    // matter, in such a way that the mock is available to providers?
    
    // Well, if the consumer is not in the same module at all, you can overwrite
    // the whole module, as others have proposed. But that won’t work for you if
    // the constant and the config (or provider constructor) were defined in app
    // code as part of one module, since that module will not have your override
    // as a dependency and therefore the queue order will still not be correct.
    // Constants are, unlike other recipes, _unshifted_ into the queue, so the
    // first registered value is always the one that sticks.

    angular
      .module('local-mock', [ 'poop' ])
      .constant('foo', 2);
  
    beforeEach(module('local-mock'));
  
    it('should still not work even if a second module is defined ... at least not in realistic cases', () => {
      module((barProvider) => {
        expect(barProvider.foo).toBe(1);
      });
  
      inject();
    });
  });

  describe('mocking constants: part 3 (how you can do it after all)', () => {
    // If we really want to do this, to the best of my knowledge we’re going to
    // need to be willing to get our hands dirty.

    const queue = angular.module('poop')._invokeQueue;

    let originalRecipe, originalIndex;

    beforeAll(() => {
      // Queue members are arrays whose members are the name of a registry,
      // the name of a registry method, and the original arguments.
      originalIndex = queue.findIndex(([ , , [ name ] ]) => name === 'foo');
      originalRecipe = queue[originalIndex];
      queue[originalIndex] = [ '$provide', 'constant', [ 'foo', 2 ] ];
    })

    afterAll(() => {
      queue[originalIndex] = originalRecipe;
    });

    beforeEach(module('poop'));

    it('should work even as far back as provider instantiation', () => {
      module(barProvider => {
        expect(barProvider.foo).toBe(2);
      });
  
      inject();
    });
  });

  describe('mocking constants: part 4 (but be sure to include the teardown)', () => {
    // But that afterAll is important! We restored the initial state of the
    // invokeQueue so that we could continue as normal in later tests.

    beforeEach(module('poop'));

    it('should only be done very carefully!', () => {
      module(barProvider => {
        expect(barProvider.foo).toBe(1);
      });
  
      inject();
    });
  });
});
<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link href="style.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.5.2/jasmine.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.5.2/jasmine-html.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.5.2/boot.js"></script>
    <script src="https://code.angularjs.org/1.6.0-rc.2/angular.js"></script>
    <script src="https://code.angularjs.org/1.6.0-rc.2/angular-mocks.js"></script>
    <script src="app.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.5.2/jasmine.css">
  </head>

  <body>
  </body>

</html>

Now, you may be wondering why one would do any of this in the first place. The OP is actually describing a really common scenario that Angular + Karma + Jasmine fails to address. The scenario is that there’s some window-provisioned configuration value that determines application behavior — like, say, enabling or disabling ‘debug mode’ — and you need to test what happens with different fixtures, yet those values, typically used for configuration, are needed early on. We can supply these window values as fixtures and then route them through the module.constant recipe to ‘angularize’ them, but we can only do this once, because Karma/Jasmine does not normally give us a fresh environment per test or even per spec. That’s okay when the value is going to be used in the run phase, but realistically, 90% of the time, environmental flags like this are going to be of interest in either the config phase or in providers.

现在,你可能想知道为什么一开始人们会这么做。OP实际上描述了一个非常常见的场景,即角+业+茉莉花未能解决。场景是有一些窗口配置的配置值来确定应用程序行为——例如,启用或禁用“调试模式”——您需要测试不同的fixture发生什么,但是这些值(通常用于配置)在早期是需要的。我们可以将这些窗口值作为fixture提供,然后将它们路由到模块中。常数食谱“angularize”他们,但我们只能做一次,因为业力/茉莉花一般不会给我们一个新的环境每测试甚至规范。没关系当价值是使用在运行阶段,但实际上,90%的时间,这样的环境标志的兴趣要配置阶段或提供者。

You could probably abstract this pattern into a more robust helper function so as to reduce the chances of messing up the baseline module state.

您可以将此模式抽象为更健壮的助手函数,以减少破坏基线模块状态的机会。

#1


25  

First of all: it seems that jasmine is not working properly in your plunkr. But I am not quite sure – maybe someone else can check this again. Nevertheless I have created a new plunkr (http://plnkr.co/edit/MkUjSLIyWbj5A2Vy6h61?p=preview) and followed these instructions: https://github.com/searls/jasmine-all.

首先:茉莉花似乎在你的柱塞中没有正常工作。但我不太确定——也许有人可以再检查一下。尽管如此,我还是创建了一个新的plunkr (http://plnkr.co/edit/mkujsliywbj5a2vy6h61?

You will see that your beforeEach code will never run. You can check this:

您将看到,您的beforeEach代码将永远不会运行。你可以检查这个:

module(function($provide) {
  console.log('you will never see this');
  $provide.constant('I18n', { FOO: "bar"});
});

You need two things:

你需要两件事:

  1. A real test in the it function – expect(true).toBe(true) is good enough

    it函数中的一个真正的测试- expect(true).toBe(true)就足够了

  2. You must use inject somewhere in your test, otherwise the function provided to module will not be called and the constant will not be set.

    您必须在测试的某个地方使用inject,否则将不调用提供给模块的函数,也不会设置常量。

If you run this code you will see "green":

如果你运行这段代码,你会看到“绿色”:

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

common.constant('I18n', 'foo');
common.config(['I18n', function(I18n) {
  console.log("common I18n " + I18n)
}]);

var app = angular.module('plunker', ['common']);
app.config(['I18n', function(I18n) {
  console.log("plunker I18n " + I18n)
}]);

describe('tests', function() {

  beforeEach(module('common'));
  beforeEach(function() {
    module(function($provide) {
      console.log('change to bar');
      $provide.constant('I18n', 'bar');
    });
  });
  beforeEach(module('plunker'));    

  it('anything looks great', inject(function($injector) {
      var i18n = $injector.get('I18n');
      expect(i18n).toBe('bar');
  }));
});

I hope it will work as you expect!

我希望它能像你期望的那样工作!

#2


5  

I think the fundamental issue is you are defining the constants right before the config block, so each time the module is loaded, whatever mock value that may exist will be overridden. My suggestion would be to separate out the constants and config into separate modules.

我认为最基本的问题是在配置块之前定义常量,因此每次加载模块时,可能存在的任何模拟值都将被覆盖。我的建议是将常量和配置分离到单独的模块中。

#3


4  

Although it seems like you can't change which object a AngularJS constant refers to after it's been defined, you can change properties of the object itself.

虽然在定义了一个AngularJS常量之后,您似乎无法更改它引用的对象,但是您可以更改对象本身的属性。

So in your case you can inject I18n as you would any other dependency, and then alter it before your test.

所以在你的情况下,你可以像其他依赖一样注入I18n,然后在你的测试之前修改它。

var I18n;

beforeEach(inject(function (_I18n_) {
  I18n = _I18n_;
});

describe('A test that needs a different value of I18n.foo', function() {
  var originalFoo;

  beforeEach(function() {
    originalFoo = I18n.foo;
    I18n.foo = 'mock-foo';
  });

  it('should do something', function() {
    // Test that depends on different value of I18n.foo;
    expect(....);
  });

  afterEach(function() {
    I18n.foo = originalFoo;
  });
});

As above, you should save the original state of the constant, and restore it after the test, to make sure that this test doesn't interfere with any others that you might have, now, or in the future.

如上所述,您应该保存常量的初始状态,并在测试之后恢复它,以确保该测试不会干扰您现在或将来可能有的其他测试。

#4


3  

You can override a module definition. I'm just throwing this out there as one more variation.

可以重写模块定义。我只是把它作为一个变量。

angular.module('config', []).constant('x', 'NORMAL CONSTANT');

// Use or load this module when testing
angular.module('config', []).constant('x', 'TESTING CONSTANT');


angular.module('common', ['config']).config(function(x){
   // x = 'TESTING CONSTANT';
});

Redefining a module will wipe out the previously defined module, often done on accident, but in this scenario can be used to your advantage (if you feel like packaging things that way). Just remember anything else defined on that module will be wiped out too, so you'd probably want it to be a constants only module, and this may be overkill for you.

重新定义一个模块将删除先前定义的模块(通常是在意外情况下完成的),但是在这个场景中,可以使用您的优势(如果您希望以这种方式打包东西)。记住,在那个模块上定义的其他东西也会被清除,所以你可能希望它只是一个常量模块,这对你来说可能是多余的。

#5


1  

I’m going to walk through a nastier solution as a series of annotated tests. This is a solution for situations where module overwriting is not an option. That includes cases where the original constant recipe and config block belong to the same module, as well as cases where the constant is employed by a provider constructor.

我将通过一系列带注释的测试来完成一个更糟糕的解决方案。这是一种解决方案,适用于不支持模块覆盖的情况。这包括原始常量配方和配置块属于同一模块的情况,以及提供者构造函数使用常量的情况。

You can run the code inline on SO (awesome, this is new to me!)

你可以内联地运行代码(太棒了,这对我来说是全新的!)

Please note the caveats about restoring prior state after the spec. I do not recommend this approach unless you both (a) have a good understanding of the Angular module lifecycle and (b) are sure you cannot test something any other way. The three queues of modules (invoke, config, run) are not considered public API, but on the other hand they have been consistent over the history of Angular.

请注意关于在规范之后恢复先前状态的注意事项。我不推荐这种方法,除非您(a)对角度模块的生命周期有很好的理解,(b)确定您不能以任何其他方式进行测试。模块的3个队列(调用、配置、运行)不被认为是公共API,但是另一方面,它们在角度的历史上是一致的。

There may well be a better way to approach this — I’m really not sure — but this is the only way I have found to date.

也许有更好的方法来解决这个问题——我真的不确定——但这是我迄今为止发现的唯一方法。

angular
  .module('poop', [])
  .constant('foo', 1)
  .provider('bar', class BarProvider {
    constructor(foo) {
      this.foo = foo;
    }

    $get(foo) {
      return { foo };
    }
  })
  .constant('baz', {})
  .config((foo, baz) => {
    baz.foo = foo;
  });

describe('mocking constants', () => {
  describe('mocking constants: part 1 (what you can and can’t do out of the box)', () => {
    beforeEach(module('poop'));
  
    it('should work in the run phase', () => {
      module($provide => {
        $provide.constant('foo', 2);
      });

      inject(foo => {
        expect(foo).toBe(2);
      });
    });

    it('...which includes service instantiations', () => {
      module($provide => {
        $provide.constant('foo', 2);
      });

      inject(bar => {
        expect(bar.foo).toBe(2);
      });
    });

    it('should work in the config phase, technically', () => {
      module($provide => {
        $provide.constant('foo', 2);
      });

      module(foo => {
        // Code passed to ngMock module is effectively an added config block.
        expect(foo).toBe(2);
      });

      inject();
    });

    it('...but only if that config is registered afterwards!', () => {
      module($provide => {
        $provide.constant('foo', 2);
      });
  
      inject(baz => {
        // Earlier we used foo in a config block that was registered before the
        // override we just did, so it did not have the new value.
        expect(baz.foo).toBe(1);
      });
    });
  
    it('...and config phase does not include provider instantiation!', () => {
      module($provide => {
        $provide.constant('foo', 2);
      });
  
      module(barProvider => {
        expect(barProvider.foo).toBe(1);
      });
  
      inject();
    });
  });

  describe('mocking constants: part 2 (why a second module may not work)', () => {
    // We usually think of there being two lifecycle phases, 'config' and 'run'.
    // But this is an incomplete picture. There are really at least two more we
    // can speak of, ‘registration’ and ‘provider instantiations’.
    //
    // 1. Registration — the initial (usually) synchronous calls to module methods
    //    that define services. Specifically, this is the period prior to app
    //    bootstrap.
    // 2. Provider preparation — unlike the resulting services, which are only
    //    instantiated on demand, providers whose recipes are functions will all
    //    be instantiated, in registration order, before anything else happens.
    // 3. After that is when the queue of config blocks runs. When we supply
    //    functions to ngMock module, it is effectively like calling
    //    module.config() (likewise calling `inject()` is like adding a run block)
    //    so even though we can mock the constant here successfully for subsequent
    //    config blocks, it’s happening _after_ all providers are created and
    //    after any config blocks that were previously queued have already run.
    // 4. After the config queue, the runtime injector is ready and the run queue
    //    is executed in order too, so this will always get the right mocks. In
    //    this phase (and onward) services are instantiated on demand, so $get
    //    methods (which includes factory and service recipes) will get the right
    //    mock too, as will module.decorator() interceptors.
  
    // So how do we mock a value before previously registered config? Or for that
    // matter, in such a way that the mock is available to providers?
    
    // Well, if the consumer is not in the same module at all, you can overwrite
    // the whole module, as others have proposed. But that won’t work for you if
    // the constant and the config (or provider constructor) were defined in app
    // code as part of one module, since that module will not have your override
    // as a dependency and therefore the queue order will still not be correct.
    // Constants are, unlike other recipes, _unshifted_ into the queue, so the
    // first registered value is always the one that sticks.

    angular
      .module('local-mock', [ 'poop' ])
      .constant('foo', 2);
  
    beforeEach(module('local-mock'));
  
    it('should still not work even if a second module is defined ... at least not in realistic cases', () => {
      module((barProvider) => {
        expect(barProvider.foo).toBe(1);
      });
  
      inject();
    });
  });

  describe('mocking constants: part 3 (how you can do it after all)', () => {
    // If we really want to do this, to the best of my knowledge we’re going to
    // need to be willing to get our hands dirty.

    const queue = angular.module('poop')._invokeQueue;

    let originalRecipe, originalIndex;

    beforeAll(() => {
      // Queue members are arrays whose members are the name of a registry,
      // the name of a registry method, and the original arguments.
      originalIndex = queue.findIndex(([ , , [ name ] ]) => name === 'foo');
      originalRecipe = queue[originalIndex];
      queue[originalIndex] = [ '$provide', 'constant', [ 'foo', 2 ] ];
    })

    afterAll(() => {
      queue[originalIndex] = originalRecipe;
    });

    beforeEach(module('poop'));

    it('should work even as far back as provider instantiation', () => {
      module(barProvider => {
        expect(barProvider.foo).toBe(2);
      });
  
      inject();
    });
  });

  describe('mocking constants: part 4 (but be sure to include the teardown)', () => {
    // But that afterAll is important! We restored the initial state of the
    // invokeQueue so that we could continue as normal in later tests.

    beforeEach(module('poop'));

    it('should only be done very carefully!', () => {
      module(barProvider => {
        expect(barProvider.foo).toBe(1);
      });
  
      inject();
    });
  });
});
<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link href="style.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.5.2/jasmine.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.5.2/jasmine-html.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.5.2/boot.js"></script>
    <script src="https://code.angularjs.org/1.6.0-rc.2/angular.js"></script>
    <script src="https://code.angularjs.org/1.6.0-rc.2/angular-mocks.js"></script>
    <script src="app.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.5.2/jasmine.css">
  </head>

  <body>
  </body>

</html>

Now, you may be wondering why one would do any of this in the first place. The OP is actually describing a really common scenario that Angular + Karma + Jasmine fails to address. The scenario is that there’s some window-provisioned configuration value that determines application behavior — like, say, enabling or disabling ‘debug mode’ — and you need to test what happens with different fixtures, yet those values, typically used for configuration, are needed early on. We can supply these window values as fixtures and then route them through the module.constant recipe to ‘angularize’ them, but we can only do this once, because Karma/Jasmine does not normally give us a fresh environment per test or even per spec. That’s okay when the value is going to be used in the run phase, but realistically, 90% of the time, environmental flags like this are going to be of interest in either the config phase or in providers.

现在,你可能想知道为什么一开始人们会这么做。OP实际上描述了一个非常常见的场景,即角+业+茉莉花未能解决。场景是有一些窗口配置的配置值来确定应用程序行为——例如,启用或禁用“调试模式”——您需要测试不同的fixture发生什么,但是这些值(通常用于配置)在早期是需要的。我们可以将这些窗口值作为fixture提供,然后将它们路由到模块中。常数食谱“angularize”他们,但我们只能做一次,因为业力/茉莉花一般不会给我们一个新的环境每测试甚至规范。没关系当价值是使用在运行阶段,但实际上,90%的时间,这样的环境标志的兴趣要配置阶段或提供者。

You could probably abstract this pattern into a more robust helper function so as to reduce the chances of messing up the baseline module state.

您可以将此模式抽象为更健壮的助手函数,以减少破坏基线模块状态的机会。