Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to override constants for module config functions in tests?

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

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'.)

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

http://plnkr.co/edit/utCuGmdRnFRUBKGqk2sD

like image 856
Joe Drew Avatar asked Jan 10 '14 18:01

Joe Drew


4 Answers

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.

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

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

  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.

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!

like image 142
michael Avatar answered Nov 17 '22 12:11

michael


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.

like image 43
yggie Avatar answered Nov 17 '22 12:11

yggie


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.

like image 3
ProLoser Avatar answered Nov 17 '22 11:11

ProLoser


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.

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

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.

like image 4
Michal Charemza Avatar answered Nov 17 '22 11:11

Michal Charemza