I have a DataService and I want to assert that the year is getting set in the query string correctly. Is there a way to spyOn the http.get call or to access it? I don't know the correct approach to testing this. I'm using Angular 2.2.0.
The DataService
constructor(private http: Http) { }
public getEnergyData(option: string): Promise<EnergyDataDto[]> {
return this.http.get(this.getEnergyDataApiUrl(option)).toPromise().then((response) => {
this.energyDataCache = this.parseEnergyDataResponse(response);
return this.energyDataCache;
}).catch(this.handleError);
}
protected getEnergyDataApiUrl(option: string) {
return `/api/solar?year=${option}`;
}
protected parseEnergyDataResponse(response: Response) {
return response.json().data;
}
dataservice.spec.ts
describe('Given the DataService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpModule],
providers: [DataService, { provide: XHRBackend, useClass: MockBackend }],
});
});
describe('When getting the energy data', () => {
let backend: MockBackend;
let service: EnergyDataService;
let fakeEnergyData: EnergyDataDto[];
let response: Response;
const makeEnergyData = () => {
let data = [];
let one = new EnergyDataDto();
one.year = 2007;
one.countryName = 'Denmark';
one.quantity = '100000';
data.push(one);
return data;
};
beforeEach(inject([Http, XHRBackend], (http: Http, be: MockBackend) => {
backend = be;
service = new EnergyDataService(http);
fakeEnergyData = makeEnergyData();
let options = new ResponseOptions({ status: 200, body: { data: fakeEnergyData } });
response = new Response(options);
}));
it('should return fake values', async(inject([], () => {
backend.connections.subscribe((c: MockConnection) => c.mockRespond(response));
service.getEnergyData('all').then(data => {
expect(data.length).toBe(1);
expect(data[0].countryName).toBe('Denmark');
});
})));
it('should use year in query string', async(inject([], () => {
spyOn(service, 'getEnergyDataApiUrl').and.callThrough();
backend.connections.subscribe((c: MockConnection) => c.mockRespond(response));
service.getEnergyData('2007').then(data => {
// I was hoping to use backendend somehow instead, but it's not in scope when I debug it.
expect((<any>service).getEnergyDataApiUrl).toHaveBeenCalledWith('/api/solar?year=2007');
});
})));
You should do this in the mockBackend.connections subscription. This is when you have access to the URL from the MockConnection
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.url).toBe(...)
c.mockRespond(response)
});
Related
I want to write a unit test for my payment service but I'm receiving this error:
source.subscribe is not a function
at ./node_modules/rxjs/src/internal/lastValueFrom.ts:60:12
This is my service
import { HttpService } from '#nestjs/axios';
import { Injectable } from '#nestjs/common';
import { lastValueFrom } from 'rxjs';
import { PaymentInfo } from 'src/utils/types/paymentInfo';
#Injectable()
export class PaymentsService {
constructor(private readonly httpService: HttpService) {}
private createHeaderWithAuth(auth, contentType = 'application/json') {
return {
headers: {
authorization: auth.replace('Bearer', '').trim(),
'Content-Type': contentType,
},
};
}
async makePayment(auth: string, paymentInfo: PaymentInfo) {
const configs = this.createHeaderWithAuth(auth);
const response = await lastValueFrom(
await this.httpService.post(
`${process.env.PAYMENT_URL}/transaction/pay`,
paymentInfo,
configs
)
).catch((error) => {
console.log(error);
throw new Error(error.response.data.message);
});
return response.data;
}
}
So with a bit of searching and tinkering found out that this is caused by my import of a rxjs function to resolve the observable setted by axios.
I've searched ways to mock this function so I can properly test my service. But none of them gave me a solution, the questions i found only revolved around functions with modules, but these have none since is imported from a third party lib.
This is my test suite:
describe('Payments Service', () => {
let service: PaymentsService;
let mockedHttpService = {
post: jest
.fn()
.mockImplementation(
async (
url: string,
paymentInfo: PaymentInfo,
header = mockedHeader
) => {
return { mockedSuccessfulResponse };
}
),
get: jest
.fn()
.mockImplementation(async (url: string, header = mockedHeader) => {
return { ...mockedSuccessfulResponse, data: mockedUserCards };
}),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
PaymentsService,
{
provide: HttpService,
useValue: mockedHttpService,
},
],
}).compile();
service = module.get<PaymentsService>(PaymentsService);
});
describe('Initialize', () => {
it('should define service', () => {
expect(service).toBeDefined();
});
describe('makePayment', () => {
it('should make a payment', async () => {
const payment = await service.makePayment(mockedAuth, mockedPaymentInfo);
expect(mockedHttpService.post).toHaveBeenCalledWith(
`${process.env.PAYMENT_URL}/transaction/pay`,
mockedPaymentInfo,
mockedHeader
);
expect(payment).toBe(mockedSuccessfulResponse);
});
});
});
Ps.: I removed the mocked objects to reduce the amount of code to read
you should use the of operator from rxjs, and drop the async keyword. Like:
.mockImplementation(
(
url: string,
paymentInfo: PaymentInfo,
header = mockedHeader
) => {
return of({ mockedSuccessfulResponse });
}
otherwise lastValueFrom won't receive an observable object.
I'm trying to set up unit tests for a sample Angular5 app using AngularFire2 (version5) google provider login, My auth service is fairly simple and it looks like this:
let authState = null;
let mockAngularFireAuth: any = {authState: Observable.of(authState)};
#Injectable()
export class AuthService {
loggedIn: boolean;
private user: Observable<firebase.User>;
constructor(
public afAuth: AngularFireAuth
) {
this.user = afAuth.authState;
this.user.subscribe(
(user) => {
if (user) {
this.loggedIn = true;
} else {
this.loggedIn = false;
}
});
}
// --------------------------------- Google Login -----------------------------------
loginWithGoogle() {
// Sign in/up with google provider
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION)
.then(() => {
this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider())
.catch((error) => {
if (error.code === 'auth/account-exists-with-different-credential') {
alert('This email address is already registered');
}
});
});
}
// ------------------------- Checks User Authentication -----------------------
isAuthenticated() {
// returns true if the user is logged in
return this.loggedIn;
}
// --------------------------------- User LogOut -----------------------------------
logOut() {
this.afAuth.auth.signOut()
.then(() => {
this.loggedIn = false;
});
}
}
I want to test my loginWithGoogle() method but I am not sure where to start. So far my auth service spec file looks like this:
describe('AuthService', () => {
let authService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
AngularFireDatabaseModule,
AngularFireModule.initializeApp(environment.firebase),
RouterTestingModule
],
providers: [
{provide: AngularFireAuth, useValue: mockAngularFireAuth},
AuthService,
]
});
inject([AuthService], (service: AuthService) => {
authService = service;
})();
});
it('should be defined', () => {
expect(authService).toBeDefined();
});
it('should return true if loggedIn is true', () => {
expect(authService.isAuthenticated()).toBeFalsy();
authService.loggedIn = true;
expect(authService.isAuthenticated()).toBeTruthy();
});
});
Any help would be appreciated.
Well, this is what I did. I mocked the AngularFireAuth and returned the promise with reject or resolve promise to be caught. I am new to jasmine and testing, so feel free to correct me if I am doing something wrong.
it('should return a rejected promise', () => {
authState = {
email: 'lanchanagupta#gmail.com',
password: 'password',
};
mockAngularFireAuth = {
auth: jasmine.createSpyObj('auth', {
'signInWithPopup': Promise.reject({
code: 'auth/account-exists-with-different-credential'
}),
}),
authState: Observable.of(authState)
};
mockAngularFireAuth.auth.signInWithPopup()
.catch((error: { code: string }) => {
expect(error.code).toBe('auth/account-exists-with-different-credential');
});
});
it('should return a resolved promise', () => {
authState = {
email: 'lanchanagupta#gmail.com',
password: 'password',
uid: 'nuDdbfbhTwgkF5C6HN5DWDflpA83'
};
mockAngularFireAuth = {
auth: jasmine.createSpyObj('auth', {
'signInWithPopup': Promise.resolve({
user: authState
}),
})
};
mockAngularFireAuth.auth.signInWithPopup()
.then(data => {
expect(data['user']).toBe(authState);
});
});
I am struggling to unit test Angular2 service which has async Http call in the constructor (now I wonder should it be here in the first place).
Example code below - the mocked call never seems to have been executed and I am not sure where should I put it in. The test fails as the property I am asserting is undefined at the time of execution. I tried with fakeAsync and tick() but that didnt work neither.
Service class:
#Injectable
export class Service {
private data: any; //some object that will be returned from server
constructor(private http: Http) {
this.http.get('url')
.map( (res: Response) => res.json())
.subscribe(res => this.data = res);
}
getId() {
return data.id;
}
}
The unit test:
describe('service test...', () => {
let service: Service;
let backend: MockBackend;
let result = { id: 123 };
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpModule],
providers: [
Service,
{
provide: Http,
useFactory: (mockBackend, options) => {
return new Http(mockBackend, options);
},
deps: [MockBackend, BaseRequestOptions]
},
MockBackend,
BaseRequestOptions
]
});
});
beforeEach(inject([Service, MockBackend], (s, mb) => {
service = s;
backend = mb;
backend.connections.subscribe((conn) => {
conn.mockRespond(new Response(new ResponseOptions({body: result})));
});
}));
describe('test...', () => {
it('should have id of 123...', async(() => {
expect(service.getId()).toEqual(123);
}));
});
});
I'm trying to mock an authentication service. I post my login credentials and get a token back, i then save the token.
Basically the test runs if i comment the line:
this.authenticationService.saveToken(res.json().token);
I injected the service in the test, and this line doesn't affect the output.
My error is "Cannot read property 'saveToken' of undefined thrown"
Here is my service:
private authSuccess(res: Response){
this.isAuthenticated = true;
this.authenticationService.saveToken(res.json().token);
return res.json();
}
public postLogin(loginData: LoginModel): Observable<any>{
let body = JSON.stringify(loginData);
var headers = new Headers();
headers.append('Content-Type', 'application/json');
let options = new RequestOptions({ headers: headers });
return this.http.post(this.loginUrl, body, options)
//success
.map(this.authSuccess)
//error
.catch(this.handleError);
}
Here is my test:
describe('login service tests', () => {
let loginService: LoginService;
let backend: MockBackend;
let injector: Injector;
let authenticationService: AuthenticationService;
beforeEach(() => {
injector = ReflectiveInjector.resolveAndCreate(<any> [
LoginService,
AuthenticationService,
BaseRequestOptions,
MockBackend,
provide(Http, {
useFactory: (mockBackend, defaultOptions) => new Http(mockBackend, defaultOptions),
deps: [MockBackend, BaseRequestOptions]
})
]);
loginService = <LoginService> injector.get(LoginService);
backend = <MockBackend> injector.get(MockBackend);
authenticationService = <AuthenticationService> injector.get(AuthenticationService)
});
afterEach(() => backend.verifyNoPendingRequests());
it('should authenticate with the web api', () => {
let loginUrl = Constants.loginUrl;
let loginData:LoginModel = new LoginModel('username', 'password');
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.url).toEqual(loginUrl);
c.mockRespond(new Response(new ResponseOptions({ body: '{"token": "mockAuth"}' })));
});
//Correct login data
loginService.postLogin(loginData).subscribe((data) => {
expect(data.token).toBe('mockAuth');
});
});
Also, how do you guys debug when running tests? console.log doesn't seem to work and neither does debugger;
Alright, so it seems the issue was calling the method in the .map section instead of executing everything directly there.
Solution:
delete authsuccess
.map((response) => {
this.isAuthenticated = true;
this.authenticationService.saveToken(response.json().token);
return response.json();
})
I m writing a Uni-test for a login Method with an HTTP.post call, like:
this.http.post( endpoint, creds, { headers: headers})
.map(res => res.json())
.subscribe(
data => this.onLoginComplete(data.access_token, credentials),
err => this.onHttpLoginFailed(err),
() => this.trace.debug(this.componentName, "Login completed.")
);
The problem is that i'm not able to simulate the error branch; everytime is called the onLoginComplete Method;
here is my test:
it("check that Atfer Login, console show an error ", inject(
[TraceService, Http, MockBackend, WsiEndpointService],
(traceService: TraceService, http: Http,
backend: MockBackend, wsiEndpoint: WsiEndpointService) => {
let tokenTest: number = 404 ;
let response: ResponseOptions = null {} // i think i have to modify this
let connection: any;
backend.connections.subscribe((c: any) => connection = c);
let authService: AuthService = new AuthService(http, Service1, Service2);
authenticationservice.login({ "username": "a", "password": "1" });
connection.mockRespond(new Response(response));
expect(ERROR);
}));
Thanks again to everyone.
First you need to override the XHRBackend class by the MockBackend one:
describe('HttpService Tests', () => {
beforeEachProviders(() => {
return [
HTTP_PROVIDERS,
provide(XHRBackend, { useClass: MockBackend }),
HttpService
];
});
(...)
});
Notice that HttpService is the service that uses the Http object and I want to test.
Then you need to inject the mockBackend and subscribe on its connections property. When a request is sent, the corresponding callback is called and you can specify the response elements like the body. The service will receive this response as the response of the call. So you'll be able to test your service method based on this.
Below I describe how to test the getItems method of the HttpService:
it('Should return a list of items', inject([XHRBackend, HttpService, Injector], (mockBackend, httpService, injector) => {
mockBackend.connections.subscribe(
(connection: MockConnection) => {
connection.mockRespond(new Response(
new ResponseOptions({
body: [ { id: '1', label: 'item1' }]
})));
});
httpService.getItems().subscribe(
items => {
expect(items).toEqual([ { id: '1', label: 'item1' }]);
});
});
});
Here is the code of getItems method of the HttpService:
#Injectable()
export class HttpService {
constructor(private http:Http) {
}
getItems(): Observable<any[]> {
return this.http.get('/items').map(res => res.json());
}
}
To simulate an error simply use the mockError method instead of the mockResponseone:
mockBackend.connections.subscribe(
(connection: MockConnection) => {
connection.mockError(new Error('some error'));
});
You can simulate an error like this:
connection.mockError(new Response(new ResponseOptions({
body: '',
status: 404,
})));
I created a small class
import {ResponseOptions, Response} from '#angular/http';
export class MockError extends Response implements Error {
name: any;
message: any;
constructor(status: number, body: string = '') {
super(new ResponseOptions({status, body}));
}
}
which can use like this
connection.mockError(new MockError(404));