Test $ log.error in provider with Angular and Jasmine

advertisements

I have a module that sends an error via $log.error(). This is working and I'm trying to finish the tests for it. My problem is that I'm unable to validate that it logs an error during my spec.

Plunkr

code:

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

app.provider('MordorProvider', function($logProvider, $injector) {
  // we gotta do this cuz we're a provider and don't have access to $log yet
  var $log = angular.injector(['ng']).get('$log');

  this.makeMeAnErrorWorthyOfMordor = function () {
    $log.error('You shall not pass!');
  }

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

test:

describe('Gandalfs last stand', function() {

  var MordorProvider,
      $log;

  beforeEach(module('plunker'));

  beforeEach(inject(function(_MordorProvider_, _$log_) {
    MordorProvider = _MordorProvider_;
    $log = _$log_;
  }));

  it('should shout out!', function() {
    MordorProvider.makeMeAnErrorWorthyOfMordor();
    expect($log.error.logs).toContain('You shall not pass!');
  });
});

I understand that my spec is using ngMock so that I have access to $log.error.logs, but if it doesn't have access to the same $log data, I'm confused on how to test this. If you open the console on that plunkr, you can see that the error is being emitted.


angular.injector creates a new injector instance and thus, new $log service instance. It isn't supposed to be used inside of Angular app in production, there are too few valid use cases for it.

This

app.provider('MordorProvider', function($logProvider, $injector) {
  // we gotta do this cuz we're a provider and don't have access to $log yet
  var $log = angular.injector(['ng']).get('$log');
  ...
});

can't be considered a workaround to use $log in provider. There's simple a reason why service instances aren't available for injection in service providers - they just aren't instantiated yet.

In this case $log variable refers to $log instance which can't be injected anywhere else and can't be tested. This is why the spec fails on $log.error.logs.

There's nothing in this service that would require provider instead of factory. When $log is injected and used in normal way, it can be safely tested with

beforeEach(module('plunker', {
  $log: {
    error: jasmine.createSpy()
  }
}));

...
expect($log.error).toHaveBeenCalledWith('You shall not pass!');

Since the usage of $window service is the only thing that makes $log unsuitable for injection into config and provider, it can be cloned and modified to evade this limitation:

angular.module('uniformLog', []).config(function ($provide, $injector, $logProvider) {
  var uniformLog = $injector.invoke($logProvider.$get, null, { $window: window });
  $provide.constant('uniformLog', uniformLog);
  // or $provide.constant('$log', ...) to override it entirely
});

angular.module('app', ['uniformLog']).config(function ($logProvider, uniformLog) {
  $logProvider.debugEnabled(false);
  uniformLog.debug('...');
});