dustinspecker / generator-ng-poly

Yeoman generator for modular AngularJS apps with Gulp and optional Polymer support
MIT License
237 stars 50 forks source link

How to properly mock required modules? #148

Closed pyarmak closed 8 years ago

pyarmak commented 9 years ago

So I wanted to give this generator a shot (I already have some of my app built but I really like the sub-generators). In any case, everything is working splendidly except one thing: unit testing. I am planning on providing a desktop version of my app using node-webkit (I guess I will need to use something like browserify later on for the webapp) thus I need to mock require() calls (I have tried using both mockery and proxyquire to no avail). The unit tests work when not using this generator but when I run the gulp unitTest command, the tests fail. When using mockery, the injector simply fails and proxyquire doesn't have any effect on the module that require pulls in (it still pulls in the non-mocked module).

Here is a quick example of the spec I want to run:

describe('loggerService Test', function() {
    var mockery = require('mockery');
    var util, winston, loggerService, customLogger;

    beforeEach(function() {
        mockery.enable({
            warnOnReplace: false,
            warnOnUnregistered: false,
            useCleanCache: true
        });
        util = jasmine.createSpyObj('util', ['inherits']);
        customLogger = jasmine.createSpy('customLogger');
        winston = jasmine.createSpyObj('winston', ['Transport', 'Logger']);
        winston.transports = {Console: null, File: null};
        spyOn(winston.transports, 'Console');
        spyOn(winston.transports, 'File');
        mockery.registerMock('util', util);
        mockery.registerMock('winston', winston);
        module('D2RM');
        inject(function(_loggerService_) {
            loggerService = _loggerService_;
        });
    });

    afterEach(function(){
        mockery.disable();
    });

    it('should call winston.transports.Console', function() {
        expect(winston.transports.Console).toHaveBeenCalled();
    });
});

Testing is very important to me so if I can't figure this out; it's a deal breaker. Thank you for your time.

dustinspecker commented 9 years ago

By default, gulp unitTest uses Karma to run unit tests in a browser environment, so require isn't supported. I don't have experience with Mockery, but it looks like it intercepts requires in source code. Do you have requires in your source code?

If you're trying to mock Angular components look into using $provide.

pyarmak commented 9 years ago

That is exactly how I mock angular components (using $provide). I only use mockery to intercept the require calls. I've also modified karma.config.js with the following option browsers: ['NodeWebkit'] having installed karma-nodewebkit-launcher (node-webkit has access to node's require architecture out of the box. Again, tested to work on another project structure).

Here is the basic setup: logger-factory.js

(function () {
  'use strict';

  /**
   * @ngdoc service
   * @name blocks.logger.factory:Logger
   *
   * @description
   *
   */
  angular
    .module('blocks.logger')
    .factory('Logger', Logger);

  function Logger() {
    var LoggerBase = new (winston.Logger)({
        transports: [
            new (winston.transports.Console)({
                handleExceptions: true,
                prettyPrint: true,
                colorize: true,
                json: false
            }),
            new (winston.transports.File)({
                filename: 'Test.log',
                level: 'info',
                maxsize: 5242880,
                maxFiles: 1,
                json: false,
                timestamp: true,
                prettyPrint: true,
                handleExceptions: true
            }),
            new (winston.transports.CustomerLogger)({
                handleExceptions: true,
                level: 'info'
            })
        ],
        exitOnError: false
    });

    return LoggerBase;
  }

  var util = require('util'),
      winston = require('winston');

  var CustomLogger = winston.transports.CustomerLogger = function (options) {
      this.name = 'customLogger';
      this.level = options.level || 'info';
  };

  util.inherits(CustomLogger, winston.Transport);

  CustomLogger.prototype.log = function (level, msg, meta, callback) {
      (meta && !jQuery.isEmptyObject(meta)) ? console[level](msg, meta) : console[level](msg);
      if(callback) callback(null, true);
  };
}());

logger-factory_test.js

/*global describe, beforeEach, it, expect, inject, module*/
'use strict';

describe('Logger test', function () {
  var mockery =  require('mockery');
  var factory, util, winston, customLogger;

  beforeEach(function() {
mockery.enable();
    util = jasmine.createSpyObj('util', ['inherits']);
    customLogger = jasmine.createSpy('customLogger');
    winston = jasmine.createSpyObj('winston', ['Transport', 'Logger']);
    winston.transports = {Console: null, File: null};
    spyOn(winston.transports, 'Console');
    spyOn(winston.transports, 'File');
    mockery.registerMock('util', util);
    mockery.registerMock('winston', winston);
  });

  beforeEach(module('blocks.logger'));

  beforeEach(inject(function (Logger) {
    factory = Logger;
  }));

  it('should have Logger factory be defined', function () {
    expect(factory).toBeDefined();
  });

  it('should call winston.transports.Console', function() {
      expect(winston.transports.Console).toHaveBeenCalled();
  });
});

When I use require() following registering the mock in the actual test file, I get back my mocked version, but when the require() fires in the actual code, it's pulling in the real dep.

Could it have something to do with the bootstraping process (dep file injection into karma config) and/or directory structure? I suppose I could use browserify and only run my tests through the browser if that would allow me to mock (I was planing on doing this later down the road anyway) but I can't really see why that would change anything. It would appear to me that perhaps the code in logger-factory.js is somehow running before the test code in logger-factory_test.js

I have no idea what else I could try so if you would be so kind as to follow my instructions to reproduce the issue and let me know how it goes, I would greatly appreciate it!

dustinspecker commented 9 years ago

Sorry for the late response. It could be the file loading order. I'll try to find some time to play around with this and Node Webkit.