Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2 RC4: Unit test component that has dependency injection a service that has its own

As Angular team is constantly upgrading/deprecating stuff in Angular 2 RC versions I encountered this problem.

I have a component that has a Dependency Injection (DI), which is actually a service (UserService in this case). This UserService of course has some DIs of its own. After updating to the latest RC4 of Angular 2 I realised that I cannot create similar tests any more.

So as the docs are not mentioning something relative here's my code (simplified for this question).

My component:

import { Component } from '@angular/core';
import { MdButton } from '@angular2-material/button';
import {
  MdIcon,
  MdIconRegistry
} from '@angular2-material/icon';
import { UserService } from '../../services/index';

@Component({
  moduleId: module.id,
  selector: 'logout-button',
  templateUrl: 'logout-button.component.html',
  styleUrls: ['logout-button.component.css'],
  providers: [MdIconRegistry, UserService],
  directives: [MdButton, MdIcon]
})
export class LogoutButtonComponent {

  constructor(public userService: UserService) {}

  /**
   * Call UserService and logout() method
   */
  logout() {
    this.userService.logout();
  }

}

Component's DI, UserService whic as you can see has some DIs (Router, AuthHttp & Http):

import { Injectable } from '@angular/core';
import {
  Http,
  Headers
} from '@angular/http';
import {
  AuthHttp,
  JwtHelper
} from 'angular2-jwt';
import { Router } from '@angular/router';
import { UMS } from '../common/index';

@Injectable()
export class UserService {

  constructor(
    private router: Router,
    private authHttp: AuthHttp,
    private http: Http) {

      this.router = router;
      this.authHttp = authHttp;
      this.http = http;
    }

    /**
     * Logs out user
     */
    public logout() {
      this.authHttp.get(UMS.url + UMS.apis.logout)
      .subscribe(
        data => this.logoutSuccess(),
        err => this.logoutSuccess()
      );
    }

}

And here's the test for the component:

import { By }           from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import {
  beforeEach,
  beforeEachProviders,
  describe,
  expect,
  it,
  inject,
  fakeAsync,
  TestComponentBuilder
} from '@angular/core/testing';

import { AuthHttp } from 'angular2-jwt';
import { Router } from '@angular/router';
import { Http } from '@angular/http';
import { LogoutButtonComponent } from './logout-button.component';
import { UserService } from '../../services/index';

describe('Component: LogoutButtonComponent', () => {



  beforeEachProviders(() => [
    LogoutButtonComponent,
    UserService
  ]);

  it('should inject UserService', inject([LogoutButtonComponent],
    (component: LogoutButtonComponent) => {
      expect(component).toBeTruthy();
  }));

});

Don't worry about the (it) for now.

As you can see I;m adding the related providers on the beforeEachProviders.

In this case I'm getting an error when I run the tests:

Error: No provider for Router! (LogoutButtonComponent -> UserService -> Router)

Which is expected let's say.

So in order to don't get those errors I'm adding the service's DIs in the providers also:

  beforeEachProviders(() => [
    LogoutButtonComponent,
    Router,
    AuthHttp,
    Http,
    UserService
  ]);

But now I'm, getting this error:

Error: Cannot resolve all parameters for 'Router'(?, ?, ?, ?, ?, ?, ?). Make sure that all the parameters are decorated with Inject or have valid type annotations and that 'Router' is decorated with Injectable.

I'm really trying to figure out what's happening so I found some related answers here but ALL are outdated and covers the old router-deprecated or angular2/router but both are deprecated and are not covering this case.

Would love some help on this and maybe some resources as I cannot find anything related to the latest version of Router: "@angular/router": "3.0.0-beta.2", and RC4.

Thanks

UPDATE!

I manage to bypass the two errors above and now I can access the component. Here's the description code:

describe('Component: LogoutButtonComponent', () => {

  let component: LogoutButtonComponent;
  let router: any = Router;
  let authHttp: any = AuthHttp;
  let http: any = Http;
  let service: any = new UserService(router, authHttp, http);

  beforeEachProviders(() => [
    LogoutButtonComponent
  ]);

  beforeEach(() => {
    component = new LogoutButtonComponent(service);
  });

  it('should inject UserService', () => {
    expect(component.userService).toBeTruthy();
  });

  it('should logout user', () => {
    localStorage.setItem('token', 'FOO');
    component.logout();
    expect(localStorage.getItem('token')).toBeUndefined();
  });

});

But it seems that even that the DI service is injected and accessible the DIs of the service are not. So now I get this error:

TypeError: this.authHttp.get is not a function

Any ideas?

like image 348
Vassilis Pits Avatar asked Jul 15 '16 13:07

Vassilis Pits


People also ask

How do you unit test a service with a dependency?

Service Dependencies As soon as you add even one dependency to your service, you need to also add it to your tests. In case of isolated tests, you will need to pass an instance of an injectable dependency class into the constructor of your service instantiation.

What can I use instead of TestBed?

TestBed. get() was deprecated as of Angular version 9. To help minimize breaking changes, Angular introduces a new function called TestBed. inject() , which you should use instead.

Which function is used to inject a service into a test function?

The inject function wraps the test spec function but lets us also inject dependencies using the parent injector in the TestBed . The first param is an array of tokens we want to resolve dependencies for, the second parameter is a function whose arguments are the resolved dependencies.

What is beforeEach in Angular testing?

beforeEach is a global function in Jasmine that runs some setup code before each spec in the test suite. In this test suite, beforeEach is used to create a testing module using the TestBed object and declares any components that would be used in this testing module.


1 Answers

It looks like you were experiencing a dependencies loop problem, because your UserSerivce also need inject AuthHttp, Http, etc... it really will be disturb once if you need test your component. My way is just create a mock UserSerivce and return the expect mocked value through UserService.logout() method, because you don't have to know what really happened in UserService, all you need is just a return value:

let MockUserService = {
  logout() {
    // return some value you need 
  }
}

Then, in test suite:

import { provide } from '@angular/core'

beforeEachProviders(() => [
  provide(UserService, {useClass: MockUserService})
])

... detail test code here

I hope this works for you. And here is a post that helps me a lot: https://developers.livechatinc.com/blog/testing-angular-2-apps-dependency-injection-and-components/

like image 200
Pinzhang Avatar answered Oct 19 '22 11:10

Pinzhang