Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2 Testing No provider for LocationStrategy

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
  }
};
like image 656
Stefan Avatar asked Oct 19 '16 07:10

Stefan


2 Answers

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:

  • Angular 2 unit testing components with routerLink for an example of testing the routerLinks in your component, if this is something of interest to you
like image 151
Paul Samsotha Avatar answered Oct 06 '22 16:10

Paul Samsotha


OK, just simply import it:

import { RouterTestingModule } from '@angular/router/testing';

and in your .spec, in beforeEach section, add it to imports array like this:

beforeEach(fakeAsync(() => {
  TestBed.configureTestingModule({
    imports: [RouterTestingModule] //<<<< add it here
   ///////
  });
}));

That will fix the error in the Unit Test...

like image 36
Alireza Avatar answered Oct 06 '22 14:10

Alireza