Search code examples
angularjstestingwebpackkarma-runnerkarma-webpack

Karma-webpack: Unknown provider: LeaveServiceProvider <- LeaveService


My app works like a charm but I can't run my tests with

$ yarn test
# node node_modules/karma/bin/karma start ./karma.conf.js --single-run

Version

    "angular-mocks": "~1.5.10",
    "karma-webpack": "^2.0.1",
    "webpack": "^1.14.0",
    "webpack-dev-server": "^1.16.2",

Error

PhantomJS 2.1.1 (Linux 0.0.0) leave API service create(): should create a leave FAILED
        Error: [$injector:unpr] Unknown provider: LeaveServiceProvider <- LeaveService
        http://errors.angularjs.org/1.5.10/$injector/unpr?p0=LeaveServiceProvider%20%3C-%20LeaveService (line 4674)
        static/app.min.js:4674:87
        getService@static/app.min.js:4827:40
        static/app.min.js:4679:49
        getService@static/app.min.js:4827:40
        injectionArgs@static/app.min.js:4852:69
        invoke@static/app.min.js:4874:32
        WorkFn@node_modules/angular-mocks/angular-mocks.js:3130:26
        inject@node_modules/angular-mocks/angular-mocks.js:3100:46
        test/leave.service.tests.js:55:23
        loaded@http://localhost:9876/context.js:151:17
        inject@node_modules/angular-mocks/angular-mocks.js:3097:28
        test/leave.service.tests.js:55:23
        loaded@http://localhost:9876/context.js:151:17
        TypeError: undefined is not an object (evaluating '$httpBackend.expectPOST') in test/leave.service.tests.js (line 64)
        test/leave.service.tests.js:64:16
        loaded@http://localhost:9876/context.js:151:17
        TypeError: undefined is not an object (evaluating '$httpBackend.verifyNoOutstandingExpectation') in test/leave.service.tests.js (line 114)
        test/leave.service.tests.js:114:16
        loaded@http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Linux 0.0.0): Executed 1 of 5 (1 FAILED) (skipped 4) ERROR (0.042 secs / 0.01 secs)

Karma.conf.js

const webpack = require('webpack');
const webpackConfig = require('./webpack.config.js');

module.exports = function (config) {
    config.set({
        basePath: './',
        frameworks: ['jasmine', 'mocha', 'chai'],
        files: [
            './static/app.min.js',
            'node_modules/angular-mocks/angular-mocks.js',
            {pattern: 'test/leave.service.tests.js'}
        ],
        preprocessors: {
            'test/leave.service.tests.js': ['webpack']
        },
        webpack: {
            module: webpackConfig.module,
            plugins: webpackConfig.plugins
        },
        webpackMiddleware: {
            stats: 'errors-only'
        },
        notifyReporter: {
            reportEachFailure: true,
            reportSuccess: false
        },
        plugins: [
            'karma-phantomjs-launcher',
            'karma-jasmine',
            'karma-webpack',
            'karma-mocha',
            'karma-chai'
        ],
        browsers: ['PhantomJS']
    });
};

webpack.conf.js

const webpack = require('webpack');
const path = require('path');

module.exports = {
    entry: {
        app: './src2/app.js'
    },
    output: {
        path: path.resolve(__dirname, './static'),
        publicPath: '/static/',
        filename: 'app.min.js'
    },
    plugins: [
        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery',
            'window.jQuery': 'jquery',
            moment: 'moment'
        })
    ],

    resolve: {
        root: path.resolve('./src2'),
        extensions: ['', '.js']
    },
    module: {
        loaders: [
            {test: /\.css$/, loader: 'style-loader!css-loader'},
            {test: /\.scss$/, loader: 'style-loader!css-loader!sass-loader'},
            {test: /\.html$/, loader: 'html-loader'},
            {test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=8192&mimetype=application/font-woff'},
            {test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=8192&mimetype=application/font-woff'},
            {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=8192&mimetype=application/octet-stream'},
            {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader'},
            {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=8192&mimetype=image/svg+xml'}
        ]
    },
    devServer: {
        port: 8080,
        proxy: {
            '/api': {
                target: {
                    host: '0.0.0.0',
                    protocol: 'http:',
                    port: 8000
                }
            }
        }
    }
};

test/leave.service.js

var chai = require('chai');
var assert = chai.assert;

describe('leave API service', function () {
    var service;
    var $httpBackend;


    beforeEach(inject(function (_$httpBackend_, LeaveService) {
        $httpBackend = _$httpBackend_;
        service = LeaveService;
    }));

    it('create(): should create a leave', function (done) {
        var foo = 'bar';

        assert.equal(foo, 'bar');
        done();
    });
});

Question

What's the matter here ?

related: Karma-webpack+angular TypeError: undefined is not an object (evaluating '$httpBackend.expectPOST'


Solution

  • I went back to a really simple test and add complexity step by step until it fails. The solution was to:

    1. inject my app angular.mock.module('app')

      beforeEach(angular.mock.module('app'));
      
    2. inject the dependencies with angular.mock.inject()

      beforeEach(function () {
          angular.mock.inject(function (_$httpBackend_, _LeaveService_) {
              $httpBackend = _$httpBackend_;
              service = _LeaveService_;
          });
      });
      
    3. load the built app, angular-mock and the test/**/*.js in karma.conf.js:

      files: [
          './static/app.min.js',
          'node_modules/angular-mocks/angular-mocks.js',
          {pattern: 'test/foo.service.tests.js'}
      ]
      

    Full code below

    foo.service.tests.js

    var chai = require('chai');
    var assert = chai.assert;
    
    describe('leave API service', function () {
        var service;
        var $httpBackend;
    
    
        beforeEach(angular.mock.module('app'));
    
        beforeEach(function () {
            angular.mock.inject(function (_$httpBackend_, _LeaveService_) {
                $httpBackend = _$httpBackend_;
                service = _LeaveService_;
            });
        });
    
        it('create(): should create a leave', function (done) {
            var foo = 'bar';
    
    
            assert.equal(foo, 'bar');
            done();
        });
    });
    

    webpack.conf.js

    const webpack = require('webpack');
    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin');
    
    module.exports = {
            entry: {
                    app: './src/app.js'
            },
            output: {
                    path: path.resolve(__dirname, './static'),
                    publicPath: '/static/',
                    filename: '[name]-[hash:8].min.js'
            },
            plugins: [
                    new webpack.optimize.ModuleConcatenationPlugin(),
                    new webpack.ProvidePlugin({
                            $: 'jquery',
                            jQuery: 'jquery',
                            moment: 'moment',
                            Util: 'exports-loader?Util!bootstrap/js/dist/util'
                    }),
                    new HtmlWebpackPlugin({
                            template: path.resolve(__dirname, './src', 'index.html'),
                            filename: path.resolve(__dirname, 'index.html'),
                            alwaysWriteToDisk: true
                    }),
                    new HtmlWebpackHarddiskPlugin()
            ],
            resolve: {alias: {Services: path.resolve(__dirname, 'src/services/')}},
            module: {
                    rules: [
                            {test: /\.css$/, use: ['style-loader', 'css-loader']},
                            {test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader']},
                            {test: /\.html$/, use: ['html-loader']},
                            {test: /src.*\.js$/, use: ['ng-annotate-loader']},
                            {test: /\.(png|jpg|jpeg|gif|ico)$/, loader: 'file-loader?name=[name].[ext]'},
                            {test: /\.(woff|woff2|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader'}
                    ]
            },
            devServer: {
                    port: 8080,
                    proxy: {
                            '/api': {target: 'http://0.0.0.0:8000'},
                            '/exports': {target: 'http://0.0.0.0:8000'},
                            '/media': {target: 'http://0.0.0.0:8000'}
                    }
            }
    };
    

    Karma.con.js

    const webpack = require('webpack');
    const webpackConfig = require('./webpack.config.js');
    
    module.exports = function (config) {
        config.set({
            basePath: './',
            frameworks: ['jasmine', 'mocha', 'chai'],
            files: [
                './static/app.min.js',
                'node_modules/angular-mocks/angular-mocks.js',
                {pattern: 'test/foo.service.tests.js'}
            ],
            preprocessors: {
                'test/foo.service.tests.js': ['webpack']
            },
            webpack: {
                module: webpackConfig.module,
                plugins: webpackConfig.plugins
            },
            webpackMiddleware: {
                stats: 'errors-only'
            },
            notifyReporter: {
                reportEachFailure: true,
                reportSuccess: false
            },
            plugins: [
                'karma-phantomjs-launcher',
                'karma-jasmine',
                'karma-webpack',
                'karma-mocha',
                'karma-chai'
            ],
            browsers: ['PhantomJS']
        });
    };