Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock components with providers in Angular 4 ? - Unit test

I have a problem mocking a component with providers in Angular 4. Here is the code :

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/core/platform-browser';
import { DebugElement } from '@angular/core';
import { FormsModule,
    ReactiveFormsModule,
    FormBuilder
} from '@angular/forms';
import { Router, RouterModule } from '@angular/router';
import {
    Http, ConnectionBackend,
    BaseRequestOptions
} from '@angular/http';
import { MockBackend, async } from '@angular/http/testing';

import { LoginComponent } from './../../../src/app/login/login.component';
import { LoginService } from './../../../src/app/login/login.service';
import { LoginComponent } from './../../../src/app/login/login.component';
import { LoggerService } from './../../../src/app/logger-service';
import { AuthService } from './../../../src/app/pages/auth.service';

describe('LoginComponent', () => {
    let comp: LoginComponent;
    let fixture: ComponentFixture<LoginComponent>;
    let de: DebugElement;
    let el: HTMLElement;

    beforeEach(() => {
        // implement mock
        class loggerService = {

        };

        class loginService = {

        };

        class authService = {

        };

        class router = {

        };

        TestBed.configureTestingModule({
            declarations: [ LoginComponent ],
            imports: [
                ReactiveFormsModule,
                FormsModule
            ],
            providers: [
                MockBackend,
                BaseRequestOptions,
                AuthService,
                LoginService,
                LoggerService,
                RouterModule,
                { provide: AuthService, useValue: authService },
                { provide: LoginService, useClass: LoginService },
                { provide: LoggerService, useValue: loggerService },
                {
                    provide: Http, useFactory: (backend: ConnectionBackend,
                        defaultOptions: BaseRequestOptions) => {
                        return new Http(backend, defaultOptions);
                    }, deps: [MockBackend, BaseRequestOptions]
                },
                { provide: Router, useClass: router }
            ]
        }).compileComponents().then(() => {
            fixture = TestBed.createComponent(LoginComponent);

            comp = fixture.componentInstance;

            comp.detectChanges();
            comp.ngOnInit();

            loginService = fixture.debugElement.injector.get(LoginService);
            loggerService = fixture.debugElement.injector.get(LoggerService);
            authService = fixture.debugElement.injector.get(AuthService);
            router = fixture.debugElement.injector.get(Router);
        });

    });

    it('should create component', async(() => {
        expect(comp).toBeDefined();
    }));
});

Here is my error:

spec-bundle.js:9 Unhandled Promise rejection: No provider for AuthService! ; Zone: ProxyZone ; Task: Promise.then ; Value: Error {__zone_symbol__error: Error at Error.ZoneAwareError (http://localhost:9876/base/config/spec-bundle.js:9:3748709) a……}

Any idea about what I am doing wrong ?

Thanks in advance :)

like image 996
Megan Avatar asked May 16 '17 12:05

Megan


People also ask

How do you mock a component in Angular unit testing?

A mock component in Angular tests can be created by MockComponent function. The mock component respects the interface of its original component, but all its methods are dummies. To create a mock component, simply pass its class into MockComponent function.

How do I mock a provider?

A mock provider is a “dummy” provider that allows you to simulate a payment flow. A mock provider is not linked to any live payment providers or third-party processors, enabling you to test your integration without registering accounts with any external processors or third-party providers.

What is mocking in unit testing Angular?

Introduction. Mocking is a great idea for testing Angular apps because it makes maintenance easier and helps reduce future bugs. There are a few complex tools, such as XUnit, for mocking an Angular CLI project. You can execute the mocking methods described in this guide only if you use vanilla Jasmine + Angular Testbed ...


1 Answers

So a couple things jump out to me. I'm not sure if they're your problem though.

You're trying to stub out empty classes, use them to mock inject into your component in place of a real service, and then assign those injected services back to the stub variables. Instead, I would try to either use the legit services, or stub them out and get a separate reference to them.

In the case of AuthService, if you want to provide the real service (even if later on you intercept and spy on its parts), you can just say

...
providers: [AuthService]
...

If you want to mock it, you would use:

class mockAuthService{}
beforeEach(() => {
    TestBed.configureTestingModule({
    ...
    providers: [{provide: AuthService, useClass: mockAuthService}] 
    ...

or

let mockAuthService;
beforeEach(() => {
    mockAuthService = {}
    TestBed.configureTestingModule({
    ...
    providers: [{provide: AuthService, useValue: mockAuthService}] 
    ...

Also, and I can't double check to verify this is an issue, you were doing this all inside the beforeEach scope, not outside it (so you can later refer to those variable, assuming you wanted to). I would move that above your beforeEach(), like I showed above/below.

Here's an example of what I mean.

describe('LoginComponent', () => {
    let comp: LoginComponent;
    let fixture: ComponentFixture<LoginComponent>;
    let de: DebugElement;
    let el: HTMLElement;


let authServiceReference;

beforeEach(() => {
    TestBed.configureTestingModule({
        declarations: [ LoginComponent ],
        imports: [
            ReactiveFormsModule,
            FormsModule
        ],
        providers: [
            MockBackend,
            BaseRequestOptions,
            AuthService,
            LoginService,
            LoggerService,
            RouterModule,                
            {
                provide: Http, useFactory: (backend: ConnectionBackend,
                    defaultOptions: BaseRequestOptions) => {
                    return new Http(backend, defaultOptions);
                }, deps: [MockBackend, BaseRequestOptions]
            }, 
            Router

        ]
    }).compileComponents().then(() => {
        fixture = TestBed.createComponent(LoginComponent);

        comp = fixture.componentInstance;

        comp.detectChanges();
        comp.ngOnInit();


        authServiceReference = Testbed.get(AuthService); // get service you injected above

    });

});

it('should create component', () => {
    expect(comp).toBeDefined();
}); // this part really doesn't need to be async. 

A few additional things (I'm relative new also, and these are things I picked up). You might find it less cluttering to simply grab a reference to the injected services at the test itself. Example:

it('should have a service', inject([SomeService], (serviceHandle: SomeService) => {
     expect(serviceHandle).toEqual(sameServiceYouTriedToGrabInInitialSetUp);
}

I hope that makes sense lol. Point being, it's much easier to just grab it there. More, you can inject as many of the services as you want to grab a handle on for that particular test.

 it('should have a service', inject([SomeService, SomeOtherService, YetOneMoreService], (serviceHandle: SomeService, otherServiceHandle: SomeOtherService, yetAnotherHandle: YetOneMoreService) => {
         spyOn(serviceHandle, 'isAuthenticated').and.returnsValue(true);
         spyOn(otherServiceHandle, 'getUrl').and.returnsValue(/home);
         let yahSpy = spyOn(yetAnotherHandle, 'doSomething');
         //code
          expect (yahSpy.doSomething).toHaveBeenCalled();

    }

Hope this helps.

like image 191
Angelo Avatar answered Jan 02 '23 04:01

Angelo