Angular 2 Unit Testing prepare mocking to share in components - unit-testing

I'm doing unit test in my Angular2 application and very new to angular2 test framework.
I have 2 components and calling same method from a service.
Firstly, I need to import the service and model to create the mock
import { MyService } from '../../shared/my.service';
import { MyModel } from '../../shared/my.model';
Secondly, arrange the model and service to mock
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
const mockMyModel: MyModel[] = [];
const mockMyService = {
sameMethod: () => Observable.of(mockMyModel),
};
Finally, mock the service
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MyComponent],
})
.overrideComponent(MyComponent, {
set: {
providers: [
{ provide: MyService , useValue: mockMyService },
],
},
})
.compileComponents();
}));
I need to do the same thing again for my another component.
Option 1: is there any way to globally share the preparation for mock
e.g. { provide: MyService , useValue: global.mockMyService }
Option 2: is it possible to have 'ServiceTestingModule' such as 'RouterTestingModule' just to mock a service.
Thanks!

Option 1 definitely can be used
myMockProvider = { provide: MyService , useValue: mockMyService };
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MyComponent],
})
.overrideComponent(MyComponent, {
set: {
providers: [myMockProvider],
},
})
.compileComponents();
}));
myMockProvider can also be a list of providers. Nested lists are resolved/flattened by providers: [...] automatically.

Related

NestJs Is not injecting value in jest unit test

I cant figure out why nestjs is not injecting value into my contstructor
Controller
export class HealthCheckController {
constructor(#Inject('HealthCheckService') private healthCheckService: HealthCheckService, private appLogger: AppLogger) {
}
Then my test
beforeEach(async () => {
module = await Test.createTestingModule({
controllers: [HealthCheckController],
providers: [
AppLoggerNope.getProvider(),
{ provide: 'HealthCheckService', useClass: HealthCheckService },
],
})
.compile();
controller = await module.resolve<HealthCheckController>(HealthCheckController);
const logger = await module.resolve<AppLogger>(AppLogger);
service = await module.resolve<HealthCheckService>('HealthCheckService');
});
NopeLogger
export class AppLoggerNope {
static getProvider() {
return {
provide: AppLogger,
useValue: {}
}
}
}
So if I debug my test I can see that instance of service is supplied but logger is null.
Now what is interesting if I comment appLogger in provider controller will be created but it will fail await module.resolve<AppLogger>(AppLogger);
You don't need to explicitly define an injection token for the HealthCheckService with #Inject, Nest will resolve it for you. You also don't need to use module.resolve unless you're using request-scoped providers. I've also pulled out the provider from your AppLoggerNope static method for simplicity. If this is working, then you can start to get fancy and move those values elsewhere.
Controller:
export class HealthCheckController {
constructor(private healthCheckService: HealthCheckService, private appLogger: AppLogger) { }
Test file:
beforeEach(async () => {
module = await Test.createTestingModule({
controllers: [HealthCheckController],
providers: [
{ provide: AppLogger, useValue: {} },
HealthCheckService,
],
})
.compile();
controller = module.get<HealthCheckController>(HealthCheckController);
const logger = module.get<AppLogger>(AppLogger);
service = module.get<HealthCheckService>(HealthCheckService);
});
Note that the HealthCheckService is supplied directly as a provider in the testing module. Unless you need to mock something or stub out some kind of functionality, you can directly use it as a provider in your testing module. If you needed to be able to assert that some method within HealthCheckService was called in a test, you may want to do something like this:
const mockedHealthCheck = { someMethod: jest.fn() };
module = await Test.createTestingModule({
controllers: [HealthCheckController],
providers: [
{ provide: AppLogger, useValue: {} },
{ provide: HealthCheckService, useValue: mockedHealthCheck,
],
})
.compile();
Then in your test you can assert expect(mockedHealthCheck.someMethod).toBeCalled().

angular 2 component with ngrx - errors when run unit-testing

I'm working on angular 2 project, using ngrx (for Redux logic) and rxjs (for observable).
Now I try to testing the project by running the auto-created .spec.ts files.
Some of the testings are fail, all of them are testing of components which use Store (from ngrx).
Here is one of them:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement } from '#angular/core';
import { MyComponent } from './overlay-menu-item.component';
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MyComponent ],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
I look after the problem, the error was:
No provider to Store
So I add: (I use jasmine for testing)
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MyComponent ],
providers: [Store]
})
.compileComponents();
}));
Now I got other error:
No provider for StateObservable
So I add:
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MyComponent ],
providers: [Store, StateObservable]
})
.compileComponents();
}));
But now I got new error that I don't know what do to with it:
Failed: Can't resolve all parameters for StateObservable: (?).
Error: Can't resolve all parameters for StateObservable: (?).
at syntaxError [mywebprojectPath]/node_modules/#angular/compiler/#angular/compiler.es5.js:1689:22)
What should I do?
You should create a MockStore.
In short:
import { Action } from '#ngrx/store';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { map } from 'rxjs/operator/map';
export class MockStore<T> extends BehaviorSubject<T> {
constructor(private _initialState: T) {
super(_initialState);
}
dispatch = (action: Action): void => {
}
select = <T, R>(pathOrMapFn: any, ...paths: string[]): Observable<R> => {
return map.call(this, pathOrMapFn);
}
}
Then you can provide the mockStore in you tests:
const initialState = {...};
TestBed.configureTestingModule({
...
providers:[
{provide:Store, useValue: new MockStore(initialState)}
]
...
})

Unit testing and mocking a service with DI

I have been struggling with this for a while, and I'm hoping someone can help. I have a component that uses a service to get data. I am attempting to add unit tests to it. My problem is that the tests always fail with "Error: No provider for Http". Here is my code:
Service:
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));
})
});
Let's try this:
First, move your providers array from your component to your NgModule. It's better to provide your services at the module level since it de-couples your providers from your component tree structure (unless you specifically want to have a separate instance of a provider per component, and from your simplified use case, there's no need for a separate 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 the NgModule that declares your ContactsComponent
#NgModule({
imports: ..
declarations: ...
providers: [ContactsService] // <-- provider definition moved to here
})
export class ModuleDeclaringContactsComponent
Once you do that, then mocking the ContactsService in your test is easy to implement.
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.
Sorry everyone - turns out it was something completely different.
I modified my code as per snorkpete's answer, and I am going to mark that as the answer, as I believe that is the cleanest approach.
The real problem came from using Angular Cli to create my project. It automatically created tests for my component and my service. This meant the code in the service test was causing the error, not the code in the component. I commented out the code in the service test and everything passed.
Annoyingly, there was no indication in any of the failures that this is where the error was coming from!
In case if component should have Service in providers we can ovverride metadata by TestBed.overrideComponent():
#Component({
selector: 'app-contacts',
templateUrl: './contacts.component.html',
styleUrls: ['./contacts.component.css'],
providers: [ContactsService] // IF YOU NEED IT
})
we need to do next:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
declarations: [ SomeComponent ],
providers: [
{provide: SomeService, useValue: SomeMock},
provideMockStore({system: {userConfig: {viewSettings: {theme: ThemeName.light}}}} as any)
],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
TestBed.overrideComponent(SomeComponent , { set: { providers: [{provide: SomeService, useValue: SomeMock}]}})
}));
beforeEach(() => {
fixture = TestBed.createComponent(SomeComponent );
component = fixture.componentInstance;
fixture.detectChanges();
});
more info here https://codecraft.tv/courses/angular/unit-testing/dependency-injection/

Is it possible to mock custom Angular 2 Material SVG icons for unit tests?

In my app's root component, I am defining custom SVG icons for md-icon. When unit testing a component that displays the custom icon I get an error. It seems that the error is likely due to the fact that my root component is not being used/initialized in my child unit test.
Is there a way to mock or add these custom icons (or md-icon) when setting up the test module? I would simply define the icons in the component I am testing, but I know other components will need them also.
The error:
Uncaught Error: Error in ./CustomerComponent class CustomerComponent - inline template:34:19 caused by: __WEBPACK_IMPORTED_MODULE_4_rxjs_Observable__.Observable.throw is not a function
Full error:
Removing the custom icons from the template solves the error.
My template is using the custom icons like this:
<md-icon svgIcon="vip">vip</md-icon>
And the root component initializes the icons like this:
this.iconRegistry.addSvgIcon(
'vip',
this.sanitizer.bypassSecurityTrustResourceUrl('assets/icons/vip.svg') as string,
);
I set up the test component like this:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
SharedModule,
CoreModule,
FormsModule,
ReactiveFormsModule,
],
providers: [
{
provide: Router,
useClass: class {
navigate = jasmine.createSpy('navigate');
},
},
{
provide: ActivatedRoute,
useValue: {
data: {
subscribe: (fn: (value: Data) => void) => fn({
customer: CUSTOMER,
company: COMPANY,
}),
},
},
},
{
provide: UtilityService,
useClass: UtilityServiceMock,
},
// etc...
],
declarations: [
CustomerComponent,
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA,
],
})
.compileComponents();
}));
Versions
Angular 2.3.0
Material 2.0.0-beta.1
I was able to use the overrideModule method to stub MdIcon. The documentation is sparse but I was able to find a GitHub issue where Angular team members discuss how to override declarations. The idea is to remove the component from the MdIconModule so that we can declare our own mock icon component.
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TestedComponent ],
imports: [
RouterTestingModule.withRoutes([]),
SharedModule,
],
})
.overrideModule(MdIconModule, {
remove: {
declarations: [MdIcon],
exports: [MdIcon]
},
add: {
declarations: [MockMdIconComponent],
exports: [MockMdIconComponent]
}
})
.compileComponents();
}));
The MockMdIconComponent is defined very simply
#Component({
selector: 'md-icon',
template: '<span></span>'
})
class MockMdIconComponent {
#Input() svgIcon: any;
#Input() fontSet: any;
#Input() fontIcon: any;
}
I used this approach because I am not importing the Material modules individually and I did not want my test to have to make Http calls to get the svg icons. The MockMdIconComponent could be declared in the testing module but I chose to declare/export it in the module override so that I could extract the object into a test helper.
Answering my own question:
After much trial/error with items like mocking the MdIconRegistry or using componentOverride() etc with no luck I no longer use a shared module within my tests.
Instead, I declare the MdIcon component directly in my testing module using a mock version of the class.
describe(`CustomerComponent`, () => {
let component: CustomerComponent;
let fixture: ComponentFixture<CustomerComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
FormsModule,
ReactiveFormsModule,
MdButtonModule,
],
providers: [
OVERLAY_PROVIDERS,
{
provide: Router,
useClass: class {
navigate = jasmine.createSpy('navigate');
},
},
{
provide: ActivatedRoute,
useValue: {
data: {
subscribe: (fn: (value: Data) => void) => fn({
customer: customer,
company: COMPANY,
}),
},
params: Observable.of({
customerId: customerId,
}),
},
},
],
declarations: [
CustomerComponent,
// Declare my own version of MdIcon here so that it is available for the CustomerComponent
MdIconMock,
],
});
fixture = TestBed.createComponent(CustomerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it(`should exist`, () => {
expect(component).toBeTruthy();
});
});
MdIconMock is simply a blank class that matches the selectors:
import { Component } from '#angular/core';
#Component({
template: '',
// tslint:disable-next-line
selector: 'md-icon, mat-icon',
})
// tslint:disable-next-line
export class MdIconMock {
}
Note: Due to TSLint rules that specify the prefix/format of class names/selectors I needed to disable TSLint for this mock.
This is a late answer. Just in case anyone come across this, there is an alternative other than OP's solution (which is nice as well):
Imports MaterialModule using forRoot()
TestBed.configureTestingModule({
declarations: [
TestComponent
],
imports: [SharedModule, MaterialModule.forRoot()],
providers: [{ provide: Router, useValue: routerStub }]
});
TestBed.compileComponents();
Get the injected MdIconRegistry and DomSanitizer
let iconRegistry = TestBed.get(MdIconRegistry);
let sanitizer = TestBed.get(DomSanitizer);
Configure them as you did in normal app
iconRegistry.addSvgIcon( 'some-icon',
sanitizer.bypassSecurityTrustResourceUrl('assets/img/some-icon.svg'));
building on #Chic's answer, since I have icons everywhere, i made a testbed patch:
import {TestBed} from '#angular/core/testing';
import {MatIconModule, MatIcon} from '#angular/material/icon';
import {Component, Input} from '#angular/core';
export function PatchTestBedMatIcons() {
const original = TestBed.configureTestingModule;
TestBed.configureTestingModule = (moduleDef) => {
return original(moduleDef)
.overrideModule(MatIconModule, {
remove: {
declarations: [MatIcon],
exports: [MatIcon]
},
add: {
declarations: [MockMatIconComponent],
exports: [MockMatIconComponent]
}
});
};
}
#Component({
selector: 'mat-icon',
template: '<span></span>'
})
class MockMatIconComponent {
#Input() svgIcon: any = null;
#Input() fontSet: any = null;
#Input() fontIcon: any = null;
}
then in your component test simply:
import {PatchTestBedMatIcons} from 'src/app/patchTestBedIcons';
PatchTestBedMatIcons();

Mock custom service in angular2 during unit test

I'm trying to write a unit test for component used in my service.
Component and service work fine.
Component:
import {Component} from '#angular/core';
import {PonyService} from '../../services';
import {Pony} from "../../models/pony.model";
#Component({
selector: 'el-ponies',
templateUrl: 'ponies.component.html',
providers: [PonyService]
})
export class PoniesComponent {
ponies: Array<Pony>;
constructor(private ponyService: PonyService) {
this.ponies = this.ponyService.getPonies(2);
}
refreshPonies() {
this.ponies = this.ponyService.getPonies(3);
}
}
Service:
import {Injectable} from "#angular/core";
import {Http} from "#angular/http";
import {Pony} from "../../models/pony.model";
#Injectable()
export class PonyService {
constructor(private http: Http) {}
getPonies(count: number): Array<Pony> {
let toReturn: Array<Pony> = [];
this.http.get('http://localhost:8080/js-backend/ponies')
.subscribe(response => {
response.json().forEach((tmp: Pony)=> { toReturn.push(tmp); });
if (count && count % 2 === 0) { toReturn.splice(0, count); }
else { toReturn.splice(count); }
});
return toReturn;
}}
Component unit test:
import {TestBed} from "#angular/core/testing";
import {PoniesComponent} from "./ponies.component";
import {PonyComponent} from "../pony/pony.component";
import {PonyService} from "../../services";
import {Pony} from "../../models/pony.model";
describe('Ponies component test', () => {
let poniesComponent: PoniesComponent;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [PoniesComponent, PonyComponent],
providers: [{provide: PonyService, useClass: MockPonyService}]
});
poniesComponent = TestBed.createComponent(PoniesComponent).componentInstance;
});
it('should instantiate component', () => {
expect(poniesComponent instanceof PoniesComponent).toBe(true, 'should create PoniesComponent');
});
});
class MockPonyService {
getPonies(count: number): Array<Pony> {
let toReturn: Array<Pony> = [];
if (count === 2) {
toReturn.push(new Pony('Rainbow Dash', 'green'));
toReturn.push(new Pony('Pinkie Pie', 'orange'));
}
if (count === 3) {
toReturn.push(new Pony('Fluttershy', 'blue'));
toReturn.push(new Pony('Rarity', 'purple'));
toReturn.push(new Pony('Applejack', 'yellow'));
}
return toReturn;
};
}
Part of package.json:
{
...
"dependencies": {
"#angular/core": "2.0.0",
"#angular/http": "2.0.0",
...
},
"devDependencies": {
"jasmine-core": "2.4.1",
"karma": "1.2.0",
"karma-jasmine": "1.0.2",
"karma-phantomjs-launcher": "1.0.2",
"phantomjs-prebuilt": "2.1.7",
...
}
}
When I execute 'karma start' I get this error
Error: Error in ./PoniesComponent class PoniesComponent_Host - inline template:0:0 caused by: No provider for Http! in config/karma-test-shim.js
It looks like karma uses PonyService instead of mocking it as MockPonyService, in spite of this line: providers: [{provide: PonyService, useClass: MockPonyService}].
The question: How I should mock the service?
It's because of this
#Component({
providers: [PonyService] <======
})
This makes it so that the service is scoped to the component, which means that Angular will create it for each component, and also means that it supercedes any global providers configured at the module level. This includes the mock provider that you configure in the test bed.
To get around this, Angular provides the TestBed.overrideComponent method, which allows us to override things like the #Component.providers and #Component.template.
TestBed.configureTestingModule({
declarations: [PoniesComponent, PonyComponent]
})
.overrideComponent(PoniesComponent, {
set: {
providers: [
{provide: PonyService, useClass: MockPonyService}
]
}
});
Another valid approach is to use tokens and rely on Intefaces instead of base classes or concrete classes, which dinosaurs like me love to do (DIP, DI, and other SOLID Blablahs). And allow your component to have its dependencies injected instead of providing it yourself in your own component.
Your component would not have any provider, it would receive the object as an interface in its constructor during angular's magic dependency injection. See #inject used in the constructor, and see the 'provide' value in providers as a text rather than a class.
So, your component would change to something like:
constructor(#Inject('PonyServiceInterface') private ponyService: IPonyService) {
this.ponies = this.ponyService.getPonies(2); }
In your #Component part, you would remove the provider and add it to a parent component such as "app.component.ts". There you would add a token:
providers: [{provide: 'PonyServiceInterface', useClass: PonyService}]
Your unit test component (the analog to app.component.ts) would have:
providers: [{provide: 'PonyServiceInterface', useClass: MockPonyService}]
So your component doesn't care what the service does, it just uses the interface, injected via the parent component (app.component.ts or your unit test component).
FYI: The #inject approach is not very widely used, and at some point it looks like angular fellows prefer baseclasses to interfaces due to how the underlying javascript works.