Angular: testing login function - javascript

I'm moving my first steps in Angular testing. In particular, I am trying to test this function in LoginComponent
login() {
this.restApi.login(this.loginForm.value).subscribe((resp) => {
this.user.username = (resp as any).user;
this.user.token = (resp as any).tkn;
this.router.navigate(['/main']);
});
}
restApi is an instance of RestApiService, a service that makes http calls. To test this function, I wrote this test:
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
let user = new UserService();
let httpMock: HttpTestingController;
let httpClient: HttpClient;
let testRouter = {
navigate: jasmine.createSpy('navigate'),
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule, FormsModule, HttpClientTestingModule],
declarations: [LoginComponent],
providers: [
{ provide: Router, useValue: testRouter },
{ provide: UserService, useValue: user },
RestApiService,
],
}).compileComponents();
httpClient = TestBed.inject(HttpClient);
httpMock = TestBed.inject(HttpTestingController);
}));
function updateForm() {
component.loginForm.controls['username'].setValue('bob');
component.loginForm.controls['password'].setValue('bobspassword');
}
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
component.ngOnInit();
fixture.detectChanges();
});
it('should login', () => {
const loginRes = {
res: 1,
spec: 'ok',
tkn: 'aToken',
user: 'bob',
};
const loginReq = {
username: 'bob',
password: 'bobspassword',
};
updateForm();
component.login();
expect(component.user.token).toBeDefined();
expect(testRouter.navigate).toHaveBeenCalledWith(['/main']);
const req = httpMock.expectOne(
'http://myServerAddress:8080/restEndpoint/login'
);
expect(req.request.method).toBe('POST');
expect(req.request.body).toEqual(loginReq);
req.flush(loginRes);
});
});
The test fails because component.user.token is undefined and it doesn't navigate to /main
Can you help me?
Thanks

This should be handled using Mock rather than making actual http call by calling the service.
export class MockRestApiService{
login(){
return of({
user: 'someName',
tkn: 'someToken'
})
}
}
Now import this Mock class in spec as below:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule, FormsModule, HttpClientTestingModule],
declarations: [LoginComponent],
providers: [
{ provide: Router, useValue: testRouter },
{ provide: UserService, useValue: user },
{provide: RestApiService, useClass: MockRestApiService},
],
}).compileComponents();
httpClient = TestBed.inject(HttpClient);
httpMock = TestBed.inject(HttpTestingController);
}));
This will return someToken as the value for the called function login()
Try to take a look at this article where I have explained a component code with proper mocking.. since you are new to testing, I would recommend this article to start with

Related

How to read config value using ConfigModule inside of a provider?

I would like to add a config module to my nestjs application and use some config in my database module.
For example I would like to get the host value of my configuration:
In a class I can do const dbHost = this.configService.get<string>('database.host');, but in my code I need the value inside of a provider. How do I get config values inside of a provider?
app.module.ts
#Module({
imports: [
DatabaseModule,
ConfigModule.forRoot({
load: [configuration],
}),
],
})
export class AppModule {}
database.module.ts
import { Module, Inject } from '#nestjs/common'
import { MongoClient, MongoClientOptions, Db, Logger } from 'mongodb'
#Module({
providers: [
{
provide: 'DATABASE_CLIENT',
useFactory: () => ({ client: null })
},
{
provide: 'DATABASE_CONNECTION',
inject: ['DATABASE_CLIENT'],
useFactory: async (dbClient): Promise<Db> => {
Logger.setLevel('debug')
// How do I get the host value of my config?
const mongo: string = 'mongodb://127.0.0.1:27017'
const database: string = 'data'
const options: MongoClientOptions = {}
const client = new MongoClient(mongo, options)
await client.connect()
const db = client.db(database)
return db
}
}
],
exports: ['DATABASE_CONNECTION', 'DATABASE_CLIENT'],
import: [ConfigModule]
})
export class DatabaseModule {
constructor(#Inject('DATABASE_CLIENT') private dbClient) {}
onApplicationShutdown(signal: string) {
if (signal) console.log(signal + ' signal recieved')
}
}
In your inject add ConfigService, then it'll be the second value of useFactory's parameters.
#Module({
providers: [
{
provide: 'DATABASE_CLIENT',
useFactory: () => ({ client: null })
},
{
provide: 'DATABASE_CONNECTION',
inject: ['DATABASE_CLIENT', ConfigService],
useFactory: async (dbClient, config: ConfigService): Promise<Db> => {
Logger.setLevel('debug')
const dbHost = config.get('database.host')
const mongo: string = 'mongodb://127.0.0.1:27017'
const database: string = 'data'
const options: MongoClientOptions = {}
const client = new MongoClient(mongo, options)
await client.connect()
const db = client.db(database)
return db
}
}
],
exports: ['DATABASE_CONNECTION', 'DATABASE_CLIENT'],
import: [ConfigModule]
})
export class DatabaseModule {
constructor(#Inject('DATABASE_CLIENT') private dbClient) {}
onApplicationShutdown(signal: string) {
if (signal) console.log(signal + ' signal recieved')
}
}

Jasmine how to test functions which returns httpClient observable?

I have a function inside a component like below
getProperties(data) {
let type = this.route.snapshot.paramMap.get('type');
const types = {
"easy": {
adviceEndpoint: this.apiService.getEasy(),
}, "medium": {
adviceEndpoint: this.apiService.getMedium(),
}
}
return types[type];
}
apiService return httpClient methods such as get,post etc.I injected successfully all dependencies as you can see above.Interestingly I can pick one of types and returns adviceEndpoint and adviceFeatures but it can't get adviceEndpoint,it returns as undefined like below when I print it to console Object{adviceEndpoint: undefined}
fdescribe('ExampleComponent', () => {
let component: ExampleComponent;
let fixture: ComponentFixture<ExampleComponent>;
let el: DebugElement;
let route: any;
beforeEach(async(() => {
const apiServiceSpy = jasmine.createSpyObj('ApiService',
["getEasy", "getMedium"]
);
TestBed.configureTestingModule({
imports: [NgbModule, CommonModule, RouterModule.forRoot([])],
declarations: [ExampleComponent],
providers: [
{ provide: ApiService, useValue: apiServiceSpy },
{
provide: ActivatedRoute,
useValue: {
snapshot: {
paramMap: convertToParamMap({ type: 'easy' })
}
}
}
]
}).compileComponents().then(() => {
fixture = TestBed.createComponent(ExampleComponent);
route = TestBed.inject(ActivatedRoute);
component = fixture.componentInstance;
el = fixture.debugElement;
fixture.detectChanges();
});
}));
it("should return true properties", () => {
const properties = component.getProperties();
console.log(properties);
//Object{adviceEndpoint: undefined}
});
});
I hope you are not trying to test your ApiService mock inside of tests for ExampleComponent. if so, then just provide some mock values, so you could check for correct object in the end
it("should return true properties", () => {
apiServiceSpy.getEasy.and.returnValue('easy-endpoint');
const properties = component.getProperties();
expect(properties).toEqual({adviceEndpoint: 'easy-endpoint'});
});

Nestjs mocking service constructor with Jest

I have created following service to use twilio send login code sms to users:
sms.service.ts
import { Injectable, Logger } from '#nestjs/common';
import * as twilio from 'twilio';
Injectable()
export class SmsService {
private twilio: twilio.Twilio;
constructor() {
this.twilio = this.getTwilio();
}
async sendLoginCode(phoneNumber: string, code: string): Promise<any> {
const smsClient = this.twilio;
const params = {
body: 'Login code: ' + code,
from: process.env.TWILIO_SENDER_NUMBER,
to: phoneNumber
};
smsClient.messages.create(params).then(message => {
return message;
});
}
getTwilio() {
return twilio(process.env.TWILIO_SID, process.env.TWILIO_SECRET);
}
}
sms.service.spec.js that contains my test
import { Test, TestingModule } from '#nestjs/testing';
import { SmsService } from './sms.service';
import { Logger } from '#nestjs/common';
describe('SmsService', () => {
let service: SmsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [SmsService]
}).compile();
service = module.get<SmsService>(SmsService);
});
describe('sendLoginCode', () => {
it('sends login code', async () => {
const mockMessage = {
test: "test"
}
jest.mock('twilio')
const twilio = require('twilio');
twilio.messages = {
create: jest.fn().mockImplementation(() => Promise.resolve(mockMessage))
}
expect(await service.sendLoginCode("4389253", "123456")).toBe(mockMessage);
});
});
});
How can I use jest create mock of the SmsService constructor so that it's twilio variable gets set to mocked version of it I create in service.spec.js?
You should inject your dependency instead of using it directly, then you can mock it in your test:
Create a custom provider
#Module({
providers: [
{
provide: 'Twillio',
useFactory: async (configService: ConfigService) =>
twilio(configService.TWILIO_SID, configService.TWILIO_SECRET),
inject: [ConfigService],
},
]
Inject it in your service
constructor(#Inject('Twillio') twillio: twilio.Twilio) {}
Mock it in your test
const module: TestingModule = await Test.createTestingModule({
providers: [
SmsService,
{ provide: 'Twillio', useFactory: twillioMockFactory },
],
}).compile();
See this thread on how to create mocks.

How to mock third party library using jest

I am developing a node.js application using nestjs
I have a class called LoggerService as below
export class LoggerService {
private logger: Rollbar;
constructor() {
this.logger = this.setupLogger();
}
critical(...args: Array<string | Error | object | Date | any[]>) {
this.logger.error(...args);
}
private setupLogger(): Rollbar {
if (this.logger == null) {
this.logger = new Rollbar({
accessToken: 'token',
environment: 'dev',
captureUncaught: true,
captureUnhandledRejections: true,
});
}
return this.logger;
}
Now I am writing unit test for this class using jest as below.
describe('LoggerService.log', () => {
let service: LoggerService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [LoggerService],
}).compile();
service = module.get<LoggerService>(LoggerService);
});
it('critical', () => {
service.critical('error','message');
expect(???).toHaveBeenCalledWith('error', 'message')
})
);
My question is how to check(expect) if logger.error is called, or how to mock Rollbar in this class.
1) Provide your external dependency/library as an injectable token in your module
#Module({
providers: [
{
provide: 'Rollbar',
useFactory: async (configService: ConfigService) => new Rollbar({
accessToken: configService.accessToken,
environment: configService.environment,
captureUncaught: true,
captureUnhandledRejections: true,
}),
inject: [ConfigService],
},
]
2) Inject it in your LoggerService instead of creating it
export class LoggerService {
constructor(#Inject('Rollbar') private logger: Rollbar) {
}
3) Now you can mock your dependency in your test
const module: TestingModule = await Test.createTestingModule({
providers: [
LoggerService,
{ provide: 'Rollbar', useFactory: rollbarMockFactory },
],
}).compile();

Jasmine spyOn service when testing Angular component

I'm trying to test my Angular component with Jasmine. The component is a simple form that submits some search criteria to a service which then goes off and does the Http stuff and returns an array of entities.
I am using Jasmine to 'spyOn' the service method and then return a mock entity. This mock entity should then be saved in a variable in the component.
The problem I am facing is that when I come to assert that the entity has been successfully returned, I am getting undefined in the entities variable which makes me think I haven't set up my spy correctly or something similar.
Any help will be greatly appreciated!
Service:
#Injectable()
export class DynamicsSearchService {
private apiUrl = '/api/DynamicsSearch/Search';
private headers = new Headers({ 'Content-Type': 'application/json' });
constructor(private http: Http) { }
search(search: DynamicsSearch): Promise<any[]> {
search.fields = this.getDefaultFields(search.entity);
return this.http
.post(this.apiUrl, JSON.stringify(search), { headers: this.headers })
.toPromise()
.then((response) => { return this.extractResults(search.entity, response.json()); })
.catch(this.handleError);
}
...
}
Component:
#Component({
selector: 'dynamics-search-component',
templateUrl: 'dynamics-search.component.html'
})
export class DynamicsSearchComponent {
...
entities: any[];
constructor(private searchService: DynamicsSearchService) { }
submitSearch() {
this.searching = this.searched = true;
this.searchService.search(this.model)
.then(results => {
this.entities = results;
this.searching = false;
this.searchSuccessful = results !== null && results.length > 0;
});
}
...
}
Test:
describe('DynamicsSearchComponent', () => {
let fixture: ComponentFixture<DynamicsSearchComponent>;
let component: DynamicsSearchComponent;
let configuration = new Configuration();
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
FormsModule,
SharedModule
],
providers: [
BaseRequestOptions,
MockBackend,
DynamicsSearchService,
Configuration,
{
provide: Http,
useFactory: (backend: ConnectionBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
},
deps: [
MockBackend,
BaseRequestOptions
]
}
],
declarations: [
DynamicsSearchComponent
]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DynamicsSearchComponent);
component = fixture.componentInstance;
});
it('on submit should get a single contact',
inject([DynamicsSearchService], (service: DynamicsSearchService) => {
var expected = [
{
contactid: 'A7806F57-002C-403F-9D3B-89778144D3E1'
}
];
const spy = spyOn(service, 'search')
.and.returnValue(Promise.resolve(expected));
component.model = new DynamicsSearch('contacts', 'A7806F57-002C-403F-9D3B-89778144D3E1', null, 'contactid');
component.submitSearch();
fixture.detectChanges();
expect(spy.calls.count()).toBe(1, `expected service search method to be called once but was called ${spy.calls.count()} times`);
expect(component.entities).toBeDefined('no entities returned');
expect(component.entities.length).toBe(1, `expected 1 entity to be returned but only ${component.entities.length} were returned`);
}
));
});
It fails on the second expect because component.entities is undefined.
You are working with Promise that is async code. Put expect into fixture.whenStable func and add async function into 'it' unit test.
fixture.whenStable().then(() => {
expect(component.entities).toBeDefined('no entities returned');
});

Categories