In my Angular2 app which uses AngularFire2, I have an AuthService which tries to authenticate anonymously with Firebase.
I am trying to write a test that expects AngularFireAuth's signInAnonymously to return a rejected promise; for authState to be null and an error to be thrown.
I an new to Jasmine and testing in general but I think I may need to be using asynchronous tests but I'm getting quite stuck.
Here is a simplified AuthService:
import { Injectable } from '#angular/core';
import { AngularFireAuth } from 'angularfire2/auth';
import * as firebase from 'firebase/app';
import { Observable } from 'rxjs/Rx';
#Injectable()
export class AuthService {
private authState: firebase.User;
constructor(private afAuth: AngularFireAuth) { this.init(); }
private init (): void {
this.afAuth.authState.subscribe((authState: firebase.User) => {
if (authState === null) {
this.afAuth.auth.signInAnonymously()
.then((authState) => {
this.authState = authState;
})
.catch((error) => {
throw new Error(error.message);
});
} else {
this.authState = authState;
}
}, (error) => {
throw new Error(error.message);
});
}
}
And here are my test specs:
import { TestBed, inject } from '#angular/core/testing';
import { AngularFireAuth } from 'angularfire2/auth';
import 'rxjs/add/observable/of';
import { Observable } from 'rxjs/Rx';
import { AuthService } from './auth.service';
import { environment } from '../environments/environment';
describe('AuthService', () => {
const mockAngularFireAuth: any = {
auth: jasmine.createSpyObj('auth', {
'signInAnonymously': Promise.resolve('foo'),
// 'signInWithPopup': Promise.reject(),
// 'signOut': Promise.reject()
}),
authState: Observable.of(null)
};
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{ provide: AngularFireAuth, useValue: mockAngularFireAuth },
{ provide: AuthService, useClass: AuthService }
]
});
});
it('should be created', inject([ AuthService ], (service: AuthService) => {
expect(service).toBeTruthy();
}));
//
//
//
//
//
describe('when we can’t authenticate', () => {
beforeEach(() => {
mockAngularFireAuth.auth.signInAnonymously.and.returnValue(Promise.reject('bar'));
});
it('should thow', inject([ AuthService ], (service: AuthService) => {
expect(mockAngularFireAuth.auth.signInAnonymously).toThrow();
}));
});
//
//
//
//
//
});
Thank you for your help!
It turns out I was mocking mockAngularFireAuth correctly. I needed to reject mockAngularFireAuth.auth signInAnonymously()'s promise with an error and expect it to be caught, a la:
import { TestBed, async, inject } from '#angular/core/testing';
import { AngularFireAuth } from 'angularfire2/auth';
import 'rxjs/add/observable/of';
import { Observable } from 'rxjs/Rx';
import { AuthService } from './auth.service';
import { MockUser} from './mock-user';
import { environment } from '../environments/environment';
describe('AuthService', () => {
// An anonymous user
const authState: MockUser = {
displayName: null,
isAnonymous: true,
uid: '17WvU2Vj58SnTz8v7EqyYYb0WRc2'
};
const mockAngularFireAuth: any = {
auth: jasmine.createSpyObj('auth', {
'signInAnonymously': Promise.reject({
code: 'auth/operation-not-allowed'
}),
// 'signInWithPopup': Promise.reject(),
// 'signOut': Promise.reject()
}),
authState: Observable.of(authState)
};
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{ provide: AngularFireAuth, useValue: mockAngularFireAuth },
{ provide: AuthService, useClass: AuthService }
]
});
});
it('should be created', inject([ AuthService ], (service: AuthService) => {
expect(service).toBeTruthy();
}));
describe('can authenticate anonymously', () => {
describe('AngularFireAuth.auth.signInAnonymously()', () => {
it('should return a resolved promise', () => {
mockAngularFireAuth.auth.signInAnonymously()
.then((data: MockUser) => {
expect(data).toEqual(authState);
});
});
});
});
describe('can’t authenticate anonymously', () => {
describe('AngularFireAuth.auth.signInAnonymously()', () => {
it('should return a rejected promise', () => {
mockAngularFireAuth.auth.signInAnonymously()
.catch((error: { code: string }) => {
expect(error.code).toEqual('auth/operation-not-allowed');
});
});
});
});
…
});
I solved this problem by doing the following:
describe('should reject promise', () => {
let resolved: boolean;
let rejected: boolean;
let _e: any;
beforeEach(function (done) {
resolved = false;
rejected = false;
// ensure conditions here are such that myFn() should return a rejected promise
service.myFn().then(() => {
resolved = true;
done();
}).catch((e) => {
rejected = true;
_e = e;
done();
});
})
it('should reject', () => {
expect(resolved).toEqual(false);
expect(rejected).toEqual(true);
expect(_e.name).toEqual("MyCustomErrorName");
});
});
Related
I'm trying to unit test a component that requires a Resolver, Router and ActivatedRoute as dependencies. I've tried to use the RouterTestingModule and mock my resolver to provide them in the testing module, but it seems to have some side effects on the creation of the component instance.
Here is the code of my component:
History.component.ts
import { Component, OnDestroy, OnInit } from '#angular/core';
import { ActivatedRoute, Router } from '#angular/router';
import { Subscription } from 'rxjs';
import { Transaction } from '../models/transaction.model';
#Component({
selector: 'app-history',
templateUrl: './history.component.html',
styleUrls: ['./history.component.scss']
})
export class HistoryComponent implements OnInit, OnDestroy {
history: Transaction[] = [];
selectedTransaction: Transaction | undefined;
subscription: Subscription = new Subscription();
constructor(
private route: ActivatedRoute,
private router: Router,
) { }
ngOnInit(): void {
this.history = this.route.snapshot.data.history;
const routeSubscription = this.route.params.subscribe((params) => {
if (params.id) {
this.setSelectedTransaction(+params.id);
}
});
this.subscription.add(routeSubscription);
}
setSelectedTransaction(transactionId: number): void {
const historyTransaction = this.history.find((transaction) => transaction.id === transactionId);
this.selectedTransaction = historyTransaction;
}
displayTransaction(transaction: Transaction): void {
this.router.navigate(['transactions', transaction.id]);
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
And here is the current unit test:
History.spec.ts
import { ComponentFixture, TestBed } from '#angular/core/testing';
import { ActivatedRoute, Resolve, Routes } from '#angular/router';
import { RouterTestingModule } from '#angular/router/testing';
import { Observable, of } from 'rxjs';
import { Transaction } from '../models/transaction.model';
import { HistoryComponent } from './history.component';
import { HistoryResolver } from './history.resolver';
const mockRawTransactions = [
{
"id":"1",
"created_at":"2016-01-01T08:30:39-0300",
"counterparty_name":"Uber",
"debit":"false",
"credit":"true",
"amount":"44.20",
"currency":"EUR",
"operation_type":"refund",
"attachements":[
{
"url":"https:\/\/fakeimg.pl\/350x200\/?text=Hello"
}
],
}
];
const mockTransactions = mockRawTransactions.map((transaction) => new Transaction(transaction));
class HistoryMockResolver implements Resolve<Transaction[]> {
resolve(): Observable<Transaction[]> {
return of(mockTransactions);
}
}
describe('HistoryComponent', () => {
const historyRoutes: Routes = [
{ path: 'transactions', component: HistoryComponent },
{ path: 'transactions/:id', component: HistoryComponent },
];
let component: HistoryComponent;
let fixture: ComponentFixture<HistoryComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HistoryComponent ],
imports: [
RouterTestingModule.withRoutes(historyRoutes),
],
providers: [
{
provide: ActivatedRoute,
useValue: {
snapshot: {
params: { id: 1 },
},
},
},
{ provide: HistoryResolver, useClass: HistoryMockResolver },
]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(HistoryComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
console.log('Component', component);
expect(component).toBeTruthy();
});
it('should display transactions', () => {
component.history = mockTransactions;
expect(component.history).toBeDefined();
expect(component.history).toHaveSize(mockRawTransactions.length);
});
it('should display a single transaction', () => {
component.history = mockTransactions;
component.displayTransaction(component.history[0]);
expect(component.selectedTransaction).toBeDefined();
expect(component.selectedTransaction).toEqual(component.history[0]);
});
});
The component is defined and well displayed in the console.log while running the tests in Karma, but Karma raises errors for each test case and evaluates the component as undefined.
Here is the first error:
TypeError: Cannot read property 'history' of undefined
at HistoryComponent.ngOnInit (http://localhost:9876/_karma_webpack_/webpack:/src/app/history/history.component.ts:22:45)
at callHook (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:2486:1)
at callHooks (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:2457:1)
at executeInitAndCheckHooks (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:2408:1)
at refreshView (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:9207:1)
at renderComponentOrTemplate (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:9306:1)
at tickRootContext (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:10532:1)
at detectChangesInRootView (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:10557:1)
at RootViewRef.detectChanges (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:22569:1)
at ComponentFixture._tick (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/testing.js:141:1)
What should I fix in my testing module ?
This is because of this.route.snapshot.data.history and data being undefined as your have not passed it in your mock activated snapshot.
You can update your provider for activated route snapshot in History.spec.ts
{
provide: ActivatedRoute,
useValue: {
snapshot: {
params: { id: 1 },
data: {history: 'something-history-obj'}
},
},
},
Or you can always use this.route.snapshot.data?.history within History.component.ts if it is indeed nullable
I have an Anuglar (version 8) application, that uses ngrx/store and RxJs for angular with this component:
export class CarComponent implements OnInit, OnDestroy {
// subscriptions
private unsubscribe$ = new Subject<void>();
constructor(
private activatedRoute: ActivatedRoute,
private readonly store: Store<any>,
private routingService: RoutingService,
private readonly carService: CarService) {}
ngOnInit(): void {
this.activatedRoute.queryParamMap.pipe(
takeUntil(this.unsubscribe$)).subscribe((paramMap: ParamMap) => {
this.store
.pipe(
select(selectDog, { dog: paramMap.get('carCode')})
)
.subscribe((car: Car) => {
this.setCar(Car);
}).unsubscribe();
});
}}
}
I have created an unit-test created with Karma and Jasmine:
import { ChangeDetectionStrategy } from '#angular/core';
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { ReactiveFormsModule } from '#angular/forms';
import { ActivatedRoute, Router } from '#angular/router';
import { RouterTestingModule } from '#angular/router/testing';
import { NgbModule } from '#ng-bootstrap/ng-bootstrap';
import { NgSelectModule } from '#ng-select/ng-select';
import { EffectsModule } from '#ngrx/effects';
import { Store, StoreModule } from '#ngrx/store';
import { Observable } from 'rxjs';
const scope = (): Scope => {
return {
carCode: '5288-5547-5247-4',
brandCar: '480',
};
};
describe('CarComponent ', () => {
let activatedRoute: ActivatedRoute;
let component: CarComponent;
let fixture: ComponentFixture<CarComponent>;
let store: Store<any>;
let routingService: RoutingService;
let router: Router;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
ReactiveFormsModule,
RouterTestingModule,
RouterTestingModule.withRoutes([]),
SharedModule,
StoreModule.forRoot({}),
StoreModule.forFeature(stayRestrictionsFeatureKey, stayRestrictionReducer),
StoreModule.forFeature(scopeFeatureKey, scopeReducer),
StoreModule.forFeature(inventoryFeatureKey, inventoryReducer),
StoreModule.forFeature(codeCarFeatureKey, codeCarReducer),
StoreModule.forFeature('scope', scopeReducer),
EffectsModule.forRoot([]),
NgbModule,
NgSelectModule,
NgOptionHighlightModule
],
providers: [{
provide: ActivatedRoute,
useValue: {
snapshot: {
queryParams: {
carCode: 'ss'
}
}
}
},
{
provide: Router,
useValue: {
routerState: {
snapshot : {
url : 'month/day/123'
}
}
}
}]
})
}));
beforeEach(() => {
activatedRoute = TestBed.get(ActivatedRoute.prototype.queryParamMap.pipe());
router = TestBed.get(Router);
routingService = TestBed.get(RoutingService);
store = TestBed.get(Store);
spyOn(store, 'dispatch').and.callThrough();
store.dispatch(new LoadScopeSuccess(scope()));
fixture = TestBed.createComponent(CarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('init', () => {
spyOn(activatedRoute.queryParamMap, 'pipe').and.returnValue(new Observable());
component.ngOnInit();
const start = DateUtil.firstOfMonth();
const end = endDate(start, 3);
fixture.whenStable().then(_ => {
expect(component.searchCriteriaForm.get('carCode').value).toEqual('5288-5547-5247-4');
expect(component.searchCriteriaForm.get('brandCode').value).toEqual('480');
});
});
});
But when I launch the test I have this error:
1) init
CarComponent
TypeError: Cannot read property 'pipe' of undefined
at Object.get queryParamMap [as queryParamMap] (http://localhost:9876/_karma_webpack_/C:/Root/root-projects/[...]/client/node_modules/#angular/router/fesm2015/router.js:2747:1)
at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/src/app/monthly-view/monthly-view.component.spec.ts:90:59)
at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/C:/Root/root-projects/[...]/client/node_modules/zone.js/dist/zone-evergreen.js:359:1)
at ProxyZoneSpec.push.../../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/C:/Root/root-projects/[...]/client/node_modules/zone.js/dist/zone-testing.js:308:
1)
at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/C:/Root/root-projects/[...]/client/node_modules/zone.js/dist/zone-evergreen.js:358:1)
at Zone.run (http://localhost:9876/_karma_webpack_/C:/Root/root-projects/[...]/client/node_modules/zone.js/dist/zone-evergreen.js:124:1)
[...]
I have tried to mock the activate route but I have not found a solution. Have you a solution in order to bypass this problem? I thank you very much.
You have to mock activatedRoute.queryParamMap and unfortunately since it's not a method, you can't use pipe. I would do this:
import { ChangeDetectionStrategy } from '#angular/core';
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { ReactiveFormsModule } from '#angular/forms';
import { ActivatedRoute, Router } from '#angular/router';
import { RouterTestingModule } from '#angular/router/testing';
import { NgbModule } from '#ng-bootstrap/ng-bootstrap';
import { NgSelectModule } from '#ng-select/ng-select';
import { EffectsModule } from '#ngrx/effects';
import { Store, StoreModule } from '#ngrx/store';
import { Observable } from 'rxjs';
const scope = (): Scope => {
return {
carCode: '5288-5547-5247-4',
brandCar: '480',
};
};
describe('CarComponent ', () => {
let activatedRoute: ActivatedRoute;
let component: CarComponent;
let fixture: ComponentFixture<CarComponent>;
let store: Store<any>;
let routingService: RoutingService;
let router: Router;
// !! add this line
const mockQueryParamMap = new BehaviorSubject<any>({
get(value: string) => {
if (value === 'carCode') {
return '123'; // your mock for `carCode`
} else {
return '456';
}
}
});
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
ReactiveFormsModule,
RouterTestingModule,
RouterTestingModule.withRoutes([]),
SharedModule,
StoreModule.forRoot({}),
StoreModule.forFeature(stayRestrictionsFeatureKey, stayRestrictionReducer),
StoreModule.forFeature(scopeFeatureKey, scopeReducer),
StoreModule.forFeature(inventoryFeatureKey, inventoryReducer),
StoreModule.forFeature(codeCarFeatureKey, codeCarReducer),
StoreModule.forFeature('scope', scopeReducer),
EffectsModule.forRoot([]),
NgbModule,
NgSelectModule,
NgOptionHighlightModule
],
providers: [{
provide: ActivatedRoute,
useValue: {
snapshot: {
queryParams: {
carCode: 'ss'
}
}
}
},
{
provide: Router,
useValue: {
routerState: {
snapshot : {
url : 'month/day/123'
}
},
// !! add this line
queryParamMap: mockQueryParamMap,
},
}]
})
}));
it('init', () => {
// this is why we use BehaviorSubject so we have full control
// on what to push into the subscription
mockQueryParamMap.next({
get(value: string) => {
if (value === 'carCode') {
return '5288-5547-5247-4'; // your mock for `carCode`
} else {
return '456';
}
}
});
component.ngOnInit();
const start = DateUtil.firstOfMonth();
const end = endDate(start, 3);
fixture.whenStable().then(_ => {
expect(component.searchCriteriaForm.get('carCode').value).toEqual('5288-5547-5247-4');
expect(component.searchCriteriaForm.get('brandCode').value).toEqual('480');
});
});
can anyone help me please? I'm trying to test a function that call a firebase functions, but when I call the main function and start to run a firebase functions, I got a error
err TypeError: Cannot read property 'emailPasswordLoginAsPromise' of null
i don't know what is happen, follow my code:
fdescribe('UserLoginContentComponent', () => {
let component: UserLoginContentComponent;
let fixture: ComponentFixture<UserLoginContentComponent>;
let loginComponent = new UserLoginContentComponent(null,null,null,null,null,null,null);
beforeAll(
async(() => {
TestBed.configureTestingModule({
imports: [
SharedModule,
AngularFireModule.initializeApp(environment.firebase),
RouterTestingModule,
BrowserAnimationsModule
],
declarations: [UserLoginContentComponent],
providers: [
AuthService,
AngularFireAuth,
AngularFirestore,
LogService,
LogPublishersService,
HttpClient,
HttpHandler
]
}).compileComponents();
fixture = TestBed.createComponent(UserLoginContentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
spyOn(loginComponent, 'onSubmit').and.callThrough();
loginComponent.loginModel.email = 'correct email';
loginComponent.loginModel.password = 'correct password';
})
);
it('component should be create', () => {
expect(component).toBeTruthy();
});
it('Correct login',function(){
loginComponent.onSubmit().then((x) => {
console.log('ok:',x)
//expect something ..
}).catch((err) => {
console.log('err',err)
})
});
});
my Function that I'm calling:
onSubmit() {
//i'm setting my loginModel in the test with email and password
console.log('this.loginModel',this.loginModel)
return new Promise((res,rej) => {
this.authService.emailPasswordLoginAsPromise(this.loginModel).then(userCredential => {
// do something..
this.authService.createOrUpdateUserDataFirestore(userCredential, null, avaliacaoChecklist, null, null).then(s =>
//updating my user or create one
}).catch(e => {
//catch if this doesn't update or create
});
});
res('login OK')
}).catch(e => {
//open a diaglog if happen something wrong...
rej('login Fail')
});
})
}
in my authService, my emailloginasPromise is like that :
emailPasswordLoginAsPromise(login) {
return new Promise((resolveEPL, rejectEPL) => {
this.angularFireAuth.auth.signInWithEmailAndPassword(login.email, login.password)
.then(credential => {
this.updateUserWithAuth(credential.user);
resolveEPL(credential.user);
}).catch(e => {
console.error('emailPasswordLogin', e);
rejectEPL(e);
});
});
}
it's my first time with testing jasmine, I studied, but i don't know how I can solve this problem, how call a async func and getting the return.
i founded the problem, follow the fix:
The authService isn't provide when i'm creating a stance of my class, so now i'm using the component:
component = fixture.componentInstance;
with this component now I'm calling my method and all providers is working.
Follow my describe:
import { ComponentFixture, TestBed } from '#angular/core/testing';
import { SharedModule } from '../../shared/shared.module';
import { UserLoginContentComponent } from './user-login-content.component';
import { AngularFireModule } from '#angular/fire';
import { environment } from 'src/environments/environment';
import { RouterTestingModule } from '#angular/router/testing';
import { AuthService } from 'src/app/core';
import { AngularFireAuth } from '#angular/fire/auth';
import { AngularFirestore } from '#angular/fire/firestore';
import { LogService } from 'src/app/shared/logger/log.service';
import { LogPublishersService } from 'src/app/shared/logger/log-publishers.service';
import { HttpClient, HttpHandler } from '#angular/common/http';
import { BrowserAnimationsModule } from '#angular/platform-browser/animations';
fdescribe('UserLoginContentComponent', () => {
let component: UserLoginContentComponent;
let fixture: ComponentFixture<UserLoginContentComponent>;
beforeAll(function(){
TestBed.configureTestingModule({
imports: [
SharedModule,
AngularFireModule.initializeApp(environment.firebase),
RouterTestingModule,
BrowserAnimationsModule
],
declarations: [UserLoginContentComponent],
providers: [
AuthService,
AngularFireAuth,
AngularFirestore,
LogService,
LogPublishersService,
HttpClient,
HttpHandler
]
}).compileComponents();
fixture = TestBed.createComponent(UserLoginContentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
});
and how to test this component?
I'm using the follow tests:
it('COrrect login',(async(done) => {
component.loginModel.email = 'correctemail#gmail.com';
component.loginModel.password = 'correctpassword';
await component.onSubmitTest().then((x) => {
expect(x).toBe('login OK');
});
done();
}));
it('Wrong login (email)',(async(done) => {
component.loginModel.email = 'wrongemail#gmail.com';
component.loginModel.password = 'correctpassword';
await component.onSubmitTest().then(() => {})
.catch((err) => {
expect(err).toBe('login Fail');
})
done();
}));
My class follow:
onSubmitTest() {
return new Promise((res,rej) => {
this.authService.emailPasswordLoginAsPromise(this.loginModel).then(() => {
res('login OK')
}).catch(e => {
rej('login Fail')
});
})
}
and my authService:
emailPasswordLoginAsPromise(login) {
return new Promise((resolveEPL, rejectEPL) => {
this.angularFireAuth.auth.signInWithEmailAndPassword(login.email, login.password)
.then(credential => {
resolveEPL(credential.user);
}).catch(e => {
rejectEPL(e);
});
});
}
And now all my testing is working with asynchronous method with firebase methods
api-connector.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpParams } from '#angular/common/http';
import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
import { Observable } from 'rxjs/Observable';
import {environment} from '../../../environments/environment';
import { catchError } from 'rxjs/operators/catchError';
#Injectable()
export class ApiConnectorService {
constructor(private http: HttpClient) { }
private getQueryString(params): string {
const queryString = Object.keys(params).map(key => key + '=' + params[key]).join('&');
console.log('QUERY STRING', queryString);
return ('?' + queryString);
}
private formatErrors(error: any) {
return new ErrorObservable(error.error);
}
get(path: string, payload: Object = {}): Observable<any> {
return this.http.get(`${environment.base_url}${path}` + this.getQueryString(payload))
.pipe(catchError(this.formatErrors));
}
put(path: string, body: Object = {}): Observable<any> {
return this.http.put(
`${environment.base_url}${path}`,
body
).pipe(catchError(this.formatErrors));
}
post(path: string, body: Object): Observable<any> {
// console.log('API SERVICE BODY', body)
return this.http.post(
`${environment.base_url}${path}`,
body
).pipe(catchError(this.formatErrors));
}
delete(path): Observable<any> {
return this.http.delete(
`${environment.base_url}${path}`
).pipe(catchError(this.formatErrors));
}
}
login.contract.ts
export interface LoginRequest {
env?: string;
userid: string;
password: string;
newpassword: string;
}
export interface LoginResponse {
token: string;
}
I am pretty new to Angular and as well Karma/Jasmine also.
I have created a simple login component and login service. While writing test cases for that purpose, I followed some docs and angular.io site. I have written some of the test cases for login component with help of docs, but I didn't manage to write test cases for login service.
How to write test cases for login service?
Here is my login.service.ts file
import { Injectable } from '#angular/core';
import { ApiConnectorService } from '../api-handlers/api-connector.service';
import { LoginRequest, LoginResponse } from './login.contract';
import { Observable } from 'rxjs/Observable';
import { map } from 'rxjs/operators';
#Injectable()
export class LoginService {
constructor(private apiConnector: ApiConnectorService) { }
login(payload: LoginRequest): Observable<LoginResponse> {
console.log('Login payload ', payload);
return this.apiConnector.post('/api/login', payload)
.pipe(
map((data: LoginResponse) => data)
)
}
}
Having had a think about it this is how I would approach testing your service. I can't do the exact details for the last test as I don't have details on your ApiConnectorService or LoginResponse object but I'm sure you'll get the idea.
import { TestBed, inject } from '#angular/core/testing';
import { LoginService } from './login.service';
import { LoginResponse, LoginRequest } from './login.contract';
import { Observable, of } from 'rxjs';
import { ApiConnectorService } from './api-connector.service';
class ApiConnectorServiceStub {
constructor() { }
post(address: string, payload: LoginRequest): Observable<LoginResponse> {
return of(new LoginResponse());
}
}
describe('LoginService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [LoginService,
{provide: ApiConnectorService, useClass: ApiConnectorServiceStub }]
});
});
it('should be created', inject([LoginService], (service: LoginService) => {
expect(service).toBeTruthy();
}));
it('should call post on apiConnectorService with right parameters when login is called',
inject([LoginService], (service: LoginService) => {
const apiConnectorStub = TestBed.get(ApiConnectorService);
const spy = spyOn(apiConnectorStub, 'post').and.returnValue(of(new LoginResponse()));
const loginRequest = of(new LoginRequest());
service.login(loginRequest);
expect(spy).toHaveBeenCalledWith('/api/login', loginRequest);
}));
it('should map data correctly when login is called', inject([LoginService], (service: LoginService) => {
const apiConnectorStub = TestBed.get(ApiConnectorService);
// Set you apiConnector output data here
const apiData = of('Test Data');
const spy = spyOn(apiConnectorStub, 'post').and.returnValue(apiData);
const result = service.login(of(new LoginRequest()));
// Set your expected LoginResponse here.
const expextedResult = of(new LoginResponse());
expect(result).toEqual(expextedResult);
}));
});
I'm trying to cover my service call in the component with test case as described below.
But getting error while running ng test as shown below:
Failed: this.monthService.getMonthView(...).then is not a function
TypeError: this.monthService.getMonthView(...).then is not a function
Service: (MonthService.ts)
getMonthView is the service call inside MonthService
getMonthView(forecastMonth: string): Promise<any[]> {
const options = new RequestOptions({ headers: this.headers });
const url = this.BaseUrl + forecastMonth;
return this.http.get(url, options)
.toPromise()
.then(response => response.json() as any[])
.catch(this.handleError);
}
Component: (MonthComponent.ts)
MonthService is imported and injected into MonthComponent.ts
loadDataOnMonthViewClicked(forecastMonth) {
this.monthService.getMonthView(forecastMonth)
.then(response =>
this.result = response
);
}
Test Suite :
beforeEach(() => {
/**
* Ref:https://angular.io/guide/testing#!#component-fixture
*/
fixture = TestBed.createComponent(MonthComponent);
component = fixture.componentInstance;
myService = TestBed.get(MonthService );
//fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
fit('should test loadDataOnMonthViewClick', async(() => {
let data=["10","20","30"];
spyOn(component.monthService, 'getMonthView').and.returnValue(result);
component.loadDataOnMonthViewClicked('Jan');
fixture.detectChanges();
expect(component.result).toBe(data);
}));
Here's what I did to make it work.
// the component
import { Component, OnInit } from "#angular/core";
import { MonthService } from "./month.service";
#Component({
selector: "app-root",
template: "",
})
export class AppComponent {
result = undefined;
constructor(private monthService: MonthService) { }
loadDataOnMonthViewClicked(forecastMonth) {
this.monthService
.getMonthView(forecastMonth)
.then(response => (this.result = response));
}
}
and the service
// the service
import { Injectable } from "#angular/core";
import { Http } from "#angular/http";
import "rxjs/add/operator/toPromise";
#Injectable()
export class MonthService {
constructor(private http: Http) { }
getMonthView(_forecastMonth: string) {
return this.http
.get("http://jsonplaceholder.typicode.com/posts/1")
.toPromise()
.then(response => response.json())
.catch(console.error);
}
}
and the test
import { TestBed, async, ComponentFixture } from "#angular/core/testing";
import { AppComponent } from "./app.component";
import { MonthService } from "./month.service";
import { HttpModule } from "#angular/http";
import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/of";
describe("AppComponent", () => {
let fixture: ComponentFixture<AppComponent>;
let component: AppComponent;
let myService;
beforeEach(
async(() => {
TestBed.configureTestingModule({
providers: [MonthService],
declarations: [AppComponent],
imports: [HttpModule],
}).compileComponents();
}),
);
beforeEach(() => {
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
myService = fixture.debugElement.injector.get(MonthService);
fixture.detectChanges();
});
it(
"should test loadDataOnMonthViewClick",
async(() => {
let data = ["10", "20", "30"];
spyOn(myService, "getMonthView").and.callFake(() =>
Promise.resolve(data),
);
component.loadDataOnMonthViewClicked("Jan");
fixture.whenStable().then(() => {
expect(component.result).toBe(data);
});
}),
);
});
But I suggest to mock the service altogether, with something like
providers: [
{ provide: FetchDataService, useClass: FetchDataServiceMock }
],