Angular2 Testing No provider for LocationStrategy

Stefan picture Stefan · Oct 19, 2016 · Viewed 18.1k times · Source

I am trying to write a test for a Component, but I always get the error: "Error: Error in ./ExpenseOverviewComponent class ExpenseOverviewComponent - inline template:41:8 caused by: No provider for Location Strategy!"

import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
import { FormsModule } from "@angular/forms";
import { RouterModule, Router, ActivatedRoute } from '@angular/router';
import { RouterStub, ActivatedRouteStub } from '../../../../utils/testutils';
import { HttpModule } from "@angular/http";
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

import { ExpenseOverviewComponent } from './expense-overview.component';
import { ExpenseFilterPipe } from '../pipes/expense-filter.pipe';
import { ExpenseService } from '../services/expense.service';

describe('ExpenseOverviewComponent', () => {
    let expenseOverviewComponent: ExpenseOverviewComponent;
    let fixture: ComponentFixture<ExpenseOverviewComponent>;
    let debugElement: DebugElement;
    let htmlElement: HTMLElement;
    let spy: jasmine.Spy;
    let expenseService: ExpenseService;

   beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [FormsModule, RouterModule, HttpModule],
            declarations: [ExpenseOverviewComponent, ExpenseFilterPipe],
            providers: [
                ExpenseService, { provide: Router, useClass: RouterStub },
                { provide: ActivatedRoute, useClass: ActivatedRouteStub }
            ]
        });

        fixture = TestBed.createComponent(ExpenseOverviewComponent);
        expenseOverviewComponent = fixture.componentInstance;
        // Expense service actually injected into the component
        expenseService = fixture.debugElement.injector.get(ExpenseService);

        // query for the title <panel-heading> by CSS class selector
        debugElement = fixture.debugElement.query(By.css('.table'));
        htmlElement = debugElement.nativeElement;
   });

   it('should not show expenses before OnInit', () => {
       spy = spyOn(expenseService, 'getExpenses').and.returnValue(new BehaviorSubject([]).asObservable());
       expect(spy.calls.any()).toBe(false, 'getExpenses not yet called');
   });
});

The component under test is the following:

import { Component, OnInit }  from '@angular/core';
import { Observable } from 'rxjs/Observable';

import { Expense } from '../model/expense';
import { ExpenseService } from '../services/expense.service';

@Component({
    template: require('./expense-overview.component.html'),
    styles: [require('./expense-overview.component.css')]
})
export class ExpenseOverviewComponent implements OnInit {

    expenseFilter: string = '';
    errorMessage: string;
    expenses: Expense[];

    constructor(private expenseService: ExpenseService) { }

    ngOnInit(): void {
        this.expenseService.getExpenses()
            .subscribe(expenses => this.expenses = expenses, error => this.errorMessage = <any>error);
    }

    deleteExpense(expense: Expense) {
        this.expenseService.deleteExpense(expense)
            .subscribe(response => {
                this.expenses = this.expenses.filter(rec => rec.id !== expense.id);
            },
            error => {
                console.error("Error deleting expense with id: " + expense.id);
                return Observable.throw(error);
            });
    }
}

Does anyone see the problem? I am bundling with webpack.

var path = require('path');
// Webpack Plugins
var ProvidePlugin = require('webpack/lib/ProvidePlugin');
var DefinePlugin  = require('webpack/lib/DefinePlugin');
var ENV = process.env.ENV = process.env.NODE_ENV = 'test';

/*
 * Config
 */
module.exports = {
  resolve: {
    cache: false,
    extensions: ['','.ts','.js','.json','.css','.html']
  },
  devtool: 'inline-source-map',
  module: {
    loaders: [
      {
        test: /\.ts$/,
        loader: 'ts-loader',
        query: {
          // remove TypeScript helpers to be injected below by DefinePlugin
          'compilerOptions': {
            'removeComments': true,
            'noEmitHelpers': true,
          },
          'ignoreDiagnostics': [
            2403, // 2403 -> Subsequent variable declarations
            2300, // 2300 Duplicate identifier
            2374, // 2374 -> Duplicate number index signature
            2375  // 2375 -> Duplicate string index signature
          ]
        },
        exclude: [ /\.e2e\.ts$/, /node_modules/ ]
      },
      { test: /\.json$/, loader: 'json-loader' },
      { test: /\.html$/, loader: 'raw-loader' },
      { test: /\.css$/, loader: 'raw-loader' },
      { test: /\.(png|jpg|jpeg|gif|svg)$/, loader: 'url', query: { limit: 25000 } }
    ],
    postLoaders: [
      // instrument only testing sources with Istanbul
      {
        test: /\.(js|ts)$/,
        include: root('src'),
        loader: 'istanbul-instrumenter-loader',
        exclude: [
          /\.e2e\.ts$/,
          /node_modules/
        ]
      }
    ],
    noParse: [
      /zone\.js\/dist\/.+/,
      /angular2\/bundles\/.+/
    ]
  },
  stats: { colors: true, reasons: true },
  debug: false,
  plugins: [
    new DefinePlugin({
      // Environment helpers
      'process.env': {
        'ENV': JSON.stringify(ENV),
        'NODE_ENV': JSON.stringify(ENV)
      },
      'global': 'window',
      // TypeScript helpers
      '__metadata': 'Reflect.metadata',
      '__decorate': 'Reflect.decorate'
    }),
    new ProvidePlugin({
      // '__metadata': 'ts-helper/metadata',
      // '__decorate': 'ts-helper/decorate',
      '__awaiter': 'ts-helper/awaiter',
      '__extends': 'ts-helper/extends',
      '__param': 'ts-helper/param',
      'Reflect': 'es7-reflect-metadata/dist/browser'
    })
  ],
    // we need this due to problems with es6-shim
  node: {
    global: 'window',
    progress: false,
    crypto: 'empty',
    module: false,
    clearImmediate: false,
    setImmediate: false
  }
};

Answer

Paul Samsotha picture Paul Samsotha · Oct 19, 2016

Instead of importing the RouterModule, you should import the RouterTestingModule from @angular/router/testing. This module factors out some things from the RouterModule that won't work in a test environment.

See also: