Unit testing and mocking the service with DI

I have been struggling with this for a while and I hope someone can help. I have a component that uses a service to get data. I am trying to add unit tests to it. My problem is that tests always fail with "Error: No Http Provider". Here is my code:

Services:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';

import { Contact } from './contact.model';

@Injectable()
export class ContactsService {
    constructor(private http: Http) { }

    public getContacts(): Observable<Array<Contact>> {
        return this.http.get('assets/contacts.json').map(res => {
            let r = res.json().Contacts;
            return r;
        });
    }
}

      

component:

import { Component, OnInit, NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Contact } from '../contact.model'; 
import { ContactsService } from '../contacts.service';

@Component({
    selector: 'app-contacts',
    templateUrl: './contacts.component.html',
    styleUrls: ['./contacts.component.css'],
    providers: [ContactsService]
})
export class ContactsComponent implements OnInit {

    contactsAll: Array<Contact>;
    contacts: Array<Contact>;

    constructor(private contactsService: ContactsService) { }

    ngOnInit() {
        this.contactsService.getContacts().subscribe((x) => {
            this.contactsAll = x;
            this.contacts = this.contactsAll;
        });
    }

      

}

Tests:

import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { Observable } from 'rxjs/Rx';

import { ContactsComponent } from './contacts.component';
import { ContactsService } from '../contacts.service';
import { Contact } from '../contact.model';

class MockContactsService extends ContactsService {

    constructor() {
        super(null);
    }

    testContacts: Array<Contact> = [
        new Contact("test1 mock", 12345, 10000),
        new Contact("test2 mock", 23456, 20000)
    ];

    public getContacts(): Observable<Array<Contact>> {
        return Observable.of(this.testContacts);
    }
}

describe('ContactsComponent', () => {
    let component: ContactsComponent;
    let fixture: ComponentFixture<ContactsComponent>;

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            imports: [FormsModule],
            declarations: [ContactsComponent],
           // providers: [{ provide: ContactsService, useClass: MockContactsService }] // this is needed for the service mock
        }).overrideComponent(ContactsService, {// The following is to override the provider in the @Component(...) metadata
            set: {
                providers: [
                    { provide: ContactsService, useClass: MockContactsService },
                ]
            }
        }).compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(ContactsComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
    });

    it('should create', () => {
        expect(component).toBeTruthy();
    });

    describe('1st test', () => {
        it('true is true', () => expect(true).toBe(true));
    })
});

      

+3


source to share


2 answers


Let's try this:

First, move the provider array from your component to your NgModule. It is better to provide your services at the module level as it decouples the providers from your component structure (unless you specifically want to have a separate provider instance per component, and from the simplified use case, there is no need for a separate provider instance per component).

So,

@Component({
    selector: 'app-contacts',
    templateUrl: './contacts.component.html',
    styleUrls: ['./contacts.component.css'],
   /// providers: [ContactsService]  <-- remove this line
})
export class ContactsComponent implements OnInit {
   .....

      

and add it to NgModule which will declare your ContactsComponent



 @NgModule({
    imports: ..
    declarations: ...
    providers: [ContactsService] // <-- provider definition moved to here
 })
 export class ModuleDeclaringContactsComponent

      

Once you do that, you can easily mock "ContactsService" in your test.

TestBed.configureTestingModule({
        imports: [FormsModule],
        declarations: [ContactsComponent],
        providers: [{ provide: ContactsService, useClass: MockContactsService }] // this is needed for the service mock
    });

      

With that, you should be good to go.

+3


source


Sorry, that's all - it turns out that it was completely different.

I modified my code to match snorkpete's answer and I'm going to mark this as an answer as I believe this is the cleanest approach.



The real problem came with using Angular Cli to build my project. It automatically generated tests for my component and my service. This meant that the code in the service test was causing the error, not the code in the component. I commented out the code in the test service and everything went well.

Annoyingly, there was no indication in any errors that this was where the error occurred!

0


source







All Articles