using MockBackend to test function that then calls .map - unit-testing

I am trying to write unit tests for my service which makes Http requests.
I have a service that returns a Http.get() request followed by a .map(). I am having trouble getting my mocked backend to return something that doesn't error on the .map(). The error I'm getting is:
this._http.get(...).map is not a function
I have been using this article as my main guide throughout.
If I remove the .map() from my service function, I don't get any errors. How can I get my mocked response to have a .map() function that I can call?
Note: I am currently using RC.4
Here is my service:
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { AppSettings } from '../../../settings';
import { Brand } from '../../models/index';
#Injectable()
export class BrandDataService {
allBrands : Brand[];
groups : any;
groupNames : string[];
constructor (
private _http : Http
) {}
/**
* Get all brands
*/
public getAllBrands () :Observable<any> {
let url = AppSettings.BRAND_API_URL + 'brands';
return this._http.get( url )
.map( this.createAndCacheBrands )
.catch( (error) => {
return Observable.throw( error );
});
}
private createAndCacheBrands (res:Response) {
...
}
}
And here is my spec file, which is using MockBackend and other associated libraries to mock the backend for these tests:
// vendor dependencies
import { Http, BaseRequestOptions, Response, ResponseOptions, RequestMethod } from '#angular/http';
import { addProviders, inject } from '#angular/core/testing';
import { MockBackend, MockConnection } from '#angular/http/testing';
// Service to test
import { BrandDataService } from './brandData.service';
describe( 'Brand data service', () => {
let service : BrandDataService = null;
let backend : MockBackend = null;
// Provide a mock backend implementation
beforeEach(() => {
addProviders([
MockBackend,
BaseRequestOptions,
{
provide : Http,
useFactory : (backendInstance : MockBackend, defaultOptions : BaseRequestOptions) => {
return new Http(backendInstance, defaultOptions);
},
deps : [MockBackend, BaseRequestOptions]
},
BrandDataService
])
})
beforeEach (inject([BrandDataService, MockBackend], (_service : BrandDataService, mockBackend : MockBackend) => {
service = _service;
backend = mockBackend;
}));
it ('should return all brands as an Observable<Response> when asked', (done) => {
// Set the mock backend to respond with the following options:
backend.connections.subscribe((connection : MockConnection) => {
// Make some expectations on the request
expect(connection.request.method).toEqual(RequestMethod.Get);
// Decide what to return
let options = new ResponseOptions({
body : JSON.stringify({
success : true
})
});
connection.mockRespond(new Response(options));
});
// Run the test.
service
.getAllBrands()
.subscribe(
(data) => {
expect(data).toBeDefined();
done();
}
)
});
});

You need to import rxjs so you can use map:
import 'rxjs/Rx';
Or, you can import only map operator so your app doesn't load files you won't use:
import 'rxjs/add/operator/map';

Related

Angular 4/CLI Unit test issue How to test a method of component that contains input object variable

Could somebody guide me how can I implement a unit test for the component below? I'm going to test the next() method of this component.When I implement a unit test for this function I got an error.Actually I'm beginner in unit test so I appreciate if somebody implements a Professional unit test on this sample which be reference for me for other components.
component file:
import { Component, Input, OnInit } from '#angular/core';
import { Client, ClientMetadata } from '../../shared/clients/client.model';
import { ClientService } from '../../shared/clients/client.service';
import { HomeRoutingService } from '../home-routing/home-routing.service';
import { FormValidationService } from "../../shared/form-validation/form-
validation.service";
import { FormBuilder} from '#angular/forms';
#Component({
selector: 'email-form',
templateUrl: './email-form.component.html',
styleUrls: ['./email-form.component.css', '../home.component.css'],
})
export class EmailFormComponent implements OnInit{
#Input() client: Client;
isClickedIncognito: boolean;
isClickedNext: boolean;
constructor(
private clientService: ClientService,
private homeRoutingService: HomeRoutingService,
public fv: FormValidationService
) { }
ngOnInit() {
this.isClickedIncognito = false;
this.isClickedNext = false;
// form is builded in fv service
this.fv.buildFormEmail();
}
next(anonymous: boolean): void {
document.body.style.cursor = 'wait';
this.client.anonymous = anonymous === true ? 1 : 0;
// If client is anonymous go directly to measurement form
if (anonymous) {
this.isClickedIncognito = true;
this.saveClient();
this.homeRoutingService.next(this.constructor.name, { anonymous:
true });
}
// Check if client exists in DB ; check if has password ;
else {
this.isClickedNext = true;
this.clientService.checkExist(this.client.email)
.then(exists =>
this.handleExist(exists)
);
}
}
saveClient(): void {
let gender = new ClientMetadata('gender', environment.gender);
(this.client.clientMetadatas = this.client.clientMetadatas ?
this.client.clientMetadatas : []).push(gender);
if (this.client.anonymous === 1)
this.client.email = null;
else if (this.client.email === null) { return; }
this.clientService.addClient(this.client)
.then(client => this.client = client);
}
}
spec file :
import { ComponentFixture, TestBed, async } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement, NO_ERRORS_SCHEMA } from '#angular/core';
import { EmailFormComponent } from './email-form.component';
import { ClientService } from '../../shared/clients/client.service';
import { HomeRoutingService } from '../home-routing/home-routing.service';
import { FormValidationService } from "../../shared/form-validation/form-
validation.service";
import { FormBuilder } from '#angular/forms';
import { Client, ClientMetadata } from '../../shared/clients/client.model';
import { TranslateModule, TranslateStaticLoader, TranslatePipe,
TranslateLoader } from 'ng2-translate';
import { Http } from '#angular/http';
export function createTranslateLoader(http: Http) {
return new TranslateStaticLoader(http, '../../assets/i18n/', '.json');
}
describe('EmailFormComponent', () => {
let component: EmailFormComponent;
let fixture: ComponentFixture<EmailFormComponent>;
let de: DebugElement;
let el: HTMLElement;
let formService: FormValidationService;
let clientService: ClientService;
let ClientEl: Client;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [EmailFormComponent],
providers: [FormValidationService,
ClientService,
HomeRoutingService,
FormValidationService,
FormBuilder
// { provide: ClientService, useValue: ClientServiceStub },
// { provide: HomeRoutingService, useValue:
HomeRoutingServiceStub },
// { provide: FormValidationService, useValue: FormValidationServiceStub }
],
schemas: [NO_ERRORS_SCHEMA],
imports: [TranslateModule.forRoot({
provide: TranslateLoader,
useFactory: (createTranslateLoader),
deps: [Http]
})]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EmailFormComponent);
component = fixture.componentInstance;
// formService = fixture.debugElement.injector.get(FormValidationService);
// formService.buildFormEmail();
// clientService = fixture.debugElement.injector.get(ClientService);
// fixture.detectChanges();
});
it('should component works well', async(() => {
const fixture = TestBed.createComponent(EmailFormComponent);
const comp = fixture.debugElement.componentInstance;
expect(comp).toBeTruthy();
}));
it('should be correct', () => {
let anonymous = true;
component.next(anonymous);
//console.log(component.isClickedIncognito);
expect(component.isClickedIncognito).toBe(true);
//expect(true).toBe(true);
});
});
error (when I comment fixture.detectChanges()):
Cannot set property 'anonymous' of undefined
error (when I put fixture.detectChanges()):
Cannot read property 'email' of undefined

Angular 2 Observable Service Karma Jasmine Unit Test not working

I am a newbie to Angular 2 and Karma + Jasmine unit tests. I cannot figure out what semantic error I have made in order to make this unit test use the mocked response. In the console, when "expect(items[0].itemId).toBe(2);" is run, it says items[0].itemId is undefined.
Would someone be able to help me out or point me in the right direction? Please let me know if you need any additional information. Thanks!
item.ts
export class Item {
itemId: number;
itemName: string;
itemDescription: string;
}
item.service.ts
import { Injectable, Inject } from '#angular/core';
import { Headers, Http } from '#angular/http';
import { Observable } from 'rxjs/Rx';
import { Item } from './item';
#Injectable()
export class ItemService {
private headers = new Headers({'Content-Type': 'application/json'});
constructor(
private http: Http)
{
}
getItems(listOptions: Object): Observable<Item[]> {
return this.http.post('/listItems', listOptions, {headers:this.headers})
.map(response => response.json() as Item[])
}
}
item.service.spec.ts
import { TestBed, fakeAsync, inject, tick } from '#angular/core/testing';
import { MockBackend } from '#angular/http/testing';
import { Http, BaseRequestOptions, Response, ResponseOptions } from '#angular/http';
import { Observable } from 'rxjs/Rx';
import { ItemService } from './item.service';
import { Item } from './item';
describe('ItemService', () => {
let mockResponse, matchingItem, connection;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
ItemService,
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (backend, defaultOptions) => new Http(backend, defaultOptions),
deps: [MockBackend, BaseRequestOptions]
},
// { provide: XHRBackend, useClass: MockBackend }
]
});
const items = [
{
"itemId":2,
"itemName":"test item1",
"itemDescription":"hello hello"
},
{
"itemId":1,
"itemName":"name2124111121",
"itemDescription":"description212412112"
}
];
mockResponse = new Response(new ResponseOptions({body: {data: items}, status: 200}));
});
describe('getItems', () => {
//Subscribing to the connection and storing it for later
it('should return all the items',inject([ItemService, MockBackend], (service: ItemService, backend: MockBackend) => {
backend.connections.subscribe(connection => {
connection.mockRespond(mockResponse);
});
service.getItems({isActive: true, sortColumn: "lastModifiedDateUtc", sortOrder: "desc"})
.subscribe((items: Item[]) => {
expect(items.length).toBe(2);
});
}));
});
});
Plunkr: https://plnkr.co/edit/m7In2eVh6oXu8VNYFf9l?p=preview
(There are some errors with the Plunkr I need help with as well but the main files are there)
The mockResponse body did not match the actual response body, that is why I was getting the error.
mockResponse = new Response(new ResponseOptions({body: {data: items}, status: 200})); should be mockResponse = new Response(new ResponseOptions({body: items, status: 200}));

How to mock non ng2 module?

I'm working with a 3rd party module in my ng2 project. I want to be able to mock this module for testing, but the module itself doesn't get injected into my service, it's just required in.
How do I overwrite this Client so that my test isn't using the actual module?
import {Injectable} from '#angular/core';
var Client = require('ssh2').Client;
#Injectable()
export class SshService {
constructor(){
//Should log "hello world"
Client.myFunc();
}
}
import { TestBed, inject } from '#angular/core/testing';
describe('My Service', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
SshService
]
});
});
it('should work as expected',
inject([SshService], (sshService:SshService) => {
sshService.Client = {
myFunc:function(){
console.log('hello world')
}
}
console.log(sshService.Client)
}));
});
You can't directly mock Client module for the test since it's required in the same file. You could wrap the Client in to a separate Angular service and inject that as a dependency:
import { Injectable } from '#angular/core';
import { TestBed, inject } from '#angular/core/testing';
let Ssh2 = require('ssh2');
#Injectable()
export class Ssh2Client {
public client: any = Ssh2.Client;
}
#Injectable()
export class Ssh2ClientMock {
// Mock your client here
public client: any = {
myFunc: () => {
console.log('hello world')
}
};
}
#Injectable()
export class SshService {
constructor(public client: Ssh2Client) {
client.myFunc();
}
}
describe('My Service', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
SshService,
{ provide: Ssh2Client, useClass: Ssh2ClientMock }
]
});
});
it('should work as expected',
inject([SshService], (sshService: SshService) => {
sshService.client.myFunc() // Should print hello world to console
})
);
});
Maybe wrap the 3rd party module in a angular2 service and inject that service in the SshService.

Unit testing Angular 2 authGuard; spy method is not being called

I'm trying to unit test my auth guard service. From this answer I was able to get this far, but now when I run the unit test for this, it says Expected spy navigate to have been called.
How to I get my spied router to be used as this.router in the service?
auth-guard.service.ts
import { Injectable } from '#angular/core';
import { Router, CanActivate } from '#angular/router';
#Injectable()
export class AuthGuardService {
constructor(private router:Router) { }
public canActivate() {
const authToken = localStorage.getItem('auth-token');
const tokenExp = localStorage.getItem('auth-token-exp');
const hasAuth = (authToken && tokenExp);
if(hasAuth && Date.now() < +tokenExp){
return true;
}
this.router.navigate(['/login']);
return false;
}
}
auth-guard.service.spec.ts
import { TestBed, async, inject } from '#angular/core/testing';
import { RouterTestingModule } from '#angular/router/testing';
import { AuthGuardService } from './auth-guard.service';
describe('AuthGuardService', () => {
let service:AuthGuardService = null;
let router = {
navigate: jasmine.createSpy('navigate')
};
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
AuthGuardService,
{provide:RouterTestingModule, useValue:router}
],
imports: [RouterTestingModule]
});
});
beforeEach(inject([AuthGuardService], (agService:AuthGuardService) => {
service = agService;
}));
it('checks if a user is valid', () => {
expect(service.canActivate()).toBeFalsy();
expect(router.navigate).toHaveBeenCalled();
});
});
Replacing RouterTestingModule with Router like in the example answer throws Unexpected value 'undefined' imported by the module 'DynamicTestModule'.
Instead of stubbing Router, use dependency injection and spy on the router.navigate() method:
import { TestBed, async, inject } from '#angular/core/testing';
import { RouterTestingModule } from '#angular/router/testing';
import { Router } from '#angular/router';
import { AuthGuardService } from './auth-guard.service';
describe('AuthGuardService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [AuthGuardService],
imports: [RouterTestingModule]
});
});
it('checks if a user is valid',
// inject your guard service AND Router
async(inject([AuthGuardService, Router], (auth, router) => {
// add a spy
spyOn(router, 'navigate');
expect(auth.canActivate()).toBeFalsy();
expect(router.navigate).toHaveBeenCalled();
})
));
});
https://plnkr.co/edit/GNjeJSQJkoelIa9AqqPp?p=preview
For this test, you can use the ReflectiveInjector to resolve and create your auth-gaurd service object with dependencies.
But instead of passing the actual Router dependency, provide your own Router class (RouterStub) that has a navigate function. Then spy on the injected Stub to check if navigate was called.
import {AuthGuardService} from './auth-guard.service';
import {ReflectiveInjector} from '#angular/core';
import {Router} from '#angular/router';
describe('AuthGuardService', () => {
let service;
let router;
beforeEach(() => {
let injector = ReflectiveInjector.resolveAndCreate([
AuthGuardService,
{provide: Router, useClass: RouterStub}
]);
service = injector.get(AuthGuardService);
router = injector.get(Router);
});
it('checks if a user is valid', () => {
let spyNavigation = spyOn(router, 'navigate');
expect(service.canActivate()).toBeFalsy();
expect(spyNavigation).toHaveBeenCalled();
expect(spyNavigation).toHaveBeenCalledWith(['/login']);
});
});
class RouterStub {
navigate(routes: string[]) {
//do nothing
}
}

How to use MockBackend in Angular2

I have this service I need to test:
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
#Injectable()
export class LoginService {
private baseUrl: string = 'http://localhost:4000/';
constructor (private http: Http) {}
public getBaseUrl() {
return this.baseUrl;
}
getLogin() {
return this.http.get(this.baseUrl + 'api/auth/octopus')
.map(res => res.json().redirect);
}
}
To test the getLogin() function I have this code:
import {
async,
describe,
it,
expect,
beforeEach,
addProviders,
inject
} from '#angular/core/testing';
import { provide} from '#angular/core';
import { LoginService } from './login.service';
import {
Http,
BaseRequestOptions,
} from '#angular/http';
import {MockBackend} from '#angular/http/testing';
describe('Service: LoginService', () => {
beforeEach(() => addProviders([
LoginService,
BaseRequestOptions,
MockBackend,
provide(Http, {
useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]
})
]));
it('should return data.',
async(inject([LoginService], (loginService: LoginService) => {
loginService.getLogin().subscribe(
data => console.log(data)
);
})
));
});
However, the data doesn't get logged.
I tried various solutions that I found on SO or on the internet.
One of them was to make a http call to the mockbackend but that just gave me data = undefined.
You need to inject MockBackend and subscribe for connection, like this:
UPDATE
async(inject([LoginService, MockBackend], (loginService: LoginService, mockBackend:MockBackend) => {
let mockResponse = new Response(new ResponseOptions({body: {'redirect': 'some string'}}))
mockBackend.connections.subscribe(c => c.mockRespond(mockResponse));
loginService.getLogin().subscribe(
data => console.log(data)
);
})
For more details go here:
https://angular.io/docs/ts/latest/api/http/testing/index/MockBackend-class.html