I am facing a problem with I have no clue how to solve it beacause I am so noobie with testing in front.
Right now I am testing a service that have this code:
import { Injectable } from '#angular/core';
import { EndpointLocatorService } from './endpointLocator.service';
import { Http, Headers } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Injectable()
export class CancelAppService {
constructor(private readonly http: Http,
private readonly _endPointLocator: EndpointLocatorService) { }
getScopeSignature(signatureToken: string) {
const url = this._endPointLocator.locate('cancelApp');
const language = sessionStorage.getItem('languageSession');
const headers = new Headers({
'Content-Type': 'application/json',
'Accept-Language': language,
'X-B3-TraceId': sessionStorage.getItem('requestID')
});
const body = JSON.stringify({ 'signatureToken': signatureToken });
return this.http.post(url, body, { headers }).map(data => {
return data.json();
}).catch((error: any) => {
return Observable.throw(new Error(error.status));
});
}
}
The testing file contains this:
import { TestBed } from '#angular/core/testing';
import { CancelAppService } from './cancelApp.service';
import { HttpModule } from '#angular/http';
import { EndpointLocatorService } from './endpointLocator.service';
import { AppConfig } from '../app.config';
import 'jasmine';
fdescribe('CancelAppService', () => {
let cancelService: CancelAppService; // Add this
let endLocator: EndpointLocatorService;
const mockData = {
"signature_token": "returnedToken"
};
const body = JSON.stringify({ 'signatureToken': 'givenToken' });
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpModule
],
providers: [
CancelAppService,
AppConfig,
EndpointLocatorService
]
});
cancelService = TestBed.get(CancelAppService); // Add this
endLocator = TestBed.get(EndpointLocatorService);
});
it('should be created', () => { // Remove inject()
expect(cancelService).toBeDefined();
});
it('should call the service', () => {
spyOn(endLocator, 'locate').and.returnValue('someUrl');
spyOn(cancelService, 'getScopeSignature').and.callThrough();
cancelService.getScopeSignature(body);
expect(cancelService.getScopeSignature).toHaveBeenCalled();
});
it('should return a object', () => {
(done: DoneFn) => {
spyOn(endLocator, 'locate').and.returnValue('someUrl');
spyOn(cancelService, 'getScopeSignature').and.returnValue(mockData);
cancelService.getScopeSignature(body).subscribe(data => {
expect(data).toEqual(mockData);
done();
});
}
});
});
The problem is that, when I try to test the returned data map, appears me like a success test, but the coverage says that I have no coveraged the lines of map, and catch.
Any idea what am I doing wrong? and how to solve it?
Thank you so much!!
You are mocking the service being tested, you should mock the service being used by your service EndpointLocatorService and maybe even HttpClient to minimize mocking. You can verify that by stepping through your code.
Let me explain in the code why those lines are not hit.
// In this example, the lines are hit, but after the test exited.
it('should call the service', () => {
spyOn(endLocator, 'locate').and.returnValue('someUrl');
spyOn(cancelService, 'getScopeSignature').and.callThrough();
// Here you are calling a method yourself and checking that it was
// called, it doesn't make sense, this test should make sure
// You should check that your endLocator was called.
// Note that because you are not waiting for the promise to resolve,
// The test finishes before either callback is handled.
cancelService.getScopeSignature(body);
expect(cancelService.getScopeSignature).toHaveBeenCalled();
});
// In this example the lines in your test are never hit because you
// mocked it
it('should return a object', () => {
(done: DoneFn) => {
spyOn(endLocator, 'locate').and.returnValue('someUrl');
// Here you are mocking getScopeSignature completely so it doesn't
// Run any of your lines that you are trying to test
// It just tests that you mocked it correctly
spyOn(cancelService, 'getScopeSignature').and.returnValue(mockData);
cancelService.getScopeSignature(body).subscribe(data => {
expect(data).toEqual(mockData);
done();
});
}
});
I can't rewrite your test so it actually tests it because it depends on how the EndpointLocatorService works and you may choose to mock it at that level also. I typically choose to mock at the HttpClient level since I can copy paste responses from the network tab.
The point is that you need to mock services used by your service, not the service itself. You have the option to mock the direct services used by your component, or you can mock at a deeper level. For example, your EndpointLocatorService may need to call HttpClient and you could choose to mock either service.
See https://angular.io/guide/testing#httpclienttestingmodule and https://alligator.io/angular/testing-httpclient/
Related
I am learning TypeScript and currently I am trying to use google API for books. Issue that I have, is that at the time when expect to get get a response I have null. However, I am logging response and data is presented console. Next strange this that, this data is logged and the end of all my code (like the last thing, but not when I am actually doing logging method).
Is there a way to wait for response? My code parts:
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
import { OrderPage } from '../order/order';
import { BooksApiProvider } from '../../providers/books-api/books-api';
#Component({
selector: 'page-book',
templateUrl: 'book.html'
})
export class BookPage {
books:any;
constructor(public navCtrl: NavController, public booksProvide: BooksApiProvider) {
}
moveToOrder() {
debugger;
this.books = this.booksProvide.getBook();
}
}
import { Injectable } from '#angular/core';
import 'rxjs/add/operator/map';
#Injectable()
export class BooksApiProvider {
constructor() {
}
getBook(){
fetch('https://www.googleapis.com/books/v1/volumes?q=isbn:9780439139601')
.then(res => res.json())
.then(data => console.log(data))
}
}
Your getBook() method doesn't return anything, therefore, this.books will always be null.
As you're using fetch here, you can return the promise in your getBook() method allowing you to perform any action with the data retrieved.
getBook(): Promise<any> {
return fetch('https://www.googleapis.com/books/v1/volumes?q=isbn:9780439139601')
.then(res => res.json())
.then(data => {
console.log(data);
return Promise.resolve(data);
});
}
Then you can call .then on getBook() to do whatever you want with the data.
(Assign the data to this.books for example)
this.booksProvide.getBook()
.then(data => this.books = data);
You're using Angular thought, you should take a look at the HttpClient. It's pretty much the same as fetch unless it deals with RxJS and Observables which are widely used in Angular.
I have a react + typescript application and I have an async api call done with axios. I want to test that async call using Jest + Enzyme.
This is what I have for my action
// load items callback
export function loadItemsSuccess(response) {
return {
type: LOAD_ITEMS,
items: response.data
};
}
// load items
export function loadItems() {
return function (dispatch) {
const authOptions = {
method: "GET",
url: "http://192.168.99.100:3000/items
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
json: true
};
return axios(authOptions).then(response => {
dispatch(loadItemsSuccess(response));
}).catch(error => {
console.log("Error loading items, error);
});
};
}
My reducer simple updates the store:
case LOAD_ITEMS:
console.log(action.items);
return action.items;
Unit testing the action should test whether the expected object is dispatched.
One way to do this is to use a combination of redux-mock-store and axios-mock-adapter.
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
// import any actions and types
const middleware = [thunk]
const mockStore = configureMockStore(middleware)
describe('loadItems', () => {
it('Dispatches LOAD_ITEMS', () => {
let mock = new MockAdapter(axios)
mock
.onGet('http://192.168.99.100:3000/items')
.reply(200, { data: 'mock' })
const store = mockStore({})
return store.dispatch(actions.loadItems())
.then(() => {
const actions = store.getActions()
expect(actions[0]).toHaveProperty('type', LOAD_ITEMS)
})
})
})
Reducers and the values in the store should be tested as a separate unit. We're just using the mockStore to dispatch the action and ensure the proper type is dispatched. MockAdapter mocks network requests. This way we can create tests around various network conditions such as 400s and timeouts.
Axios Mock Adapter
Redux Mock Store
I use HTTP requests to get data for my Vue.js application. I have one file called Api.js with the base axios instance:
export default () => {
return axios.create({
baseURL: apiURL,
headers: {
Authorization: `JWT ${store.state.token}`
}
})
}
than I have a file called service.js, which contains the functions for the different endpoints:
export default {
status() {
return Api().get('status/')
}
}
In the .vue file I call the method like that.
created() {
Service.status()
.then(response => {
// do something with the data
})
.catch(e => {
// show exception
})
}
Some exceptions should be handled in Api.js (for example: 401), some other exceptions should be handled in service.js and others in the .vue file. How can I do that?
Disclaimer: I have created two small axios plugins to achieve this specific pattern easily.
axios-middleware
Simple axios HTTP middleware service to simplify hooking to HTTP requests made through Axios.
It uses axios interceptors as mentioned by acdcjunior but it abstracts the use of axios with a commonly known middleware pattern so your app doesn't need to know and deal with the interceptor syntax.
// import your API's axios instance
import http from './api';
import { Service } from 'axios-middleware';
// Create a new service instance
const service = new Service(http);
// We're good to go!
export default service;
You can then use this middleware service to register different middlewares anywhere in your app. A middleware can be as simple as an object or a reusable, easily testable class.
import i18n from './services/i18n';
import toast from './services/toast';
import service from './services/middleware';
import { ApiErrorMiddleware, OtherMiddleware } from './middlewares';
// Then register your middleware instances.
service.register([
// Middleware class instance
new ApiErrorMiddleware(i18n, toast),
new OtherMiddleware(),
// or a simple object
{
onRequest() {
// handle the request
},
onResponseError(error) {
// handle the response error
}
}
]);
Where the ApiErrorMiddleware would be a simple class with the sole responsibility of showing toast messages on error.
export default class ApiErrorMiddleware {
/**
* #param {VueI18n} i18n instance
* #param {Object} toast message service
*/
constructor(i18n, toast) {
this.toast = toast;
this.i18n = i18n;
}
/**
* #param {Object} error
*/
onResponseError(error = {}) {
const { response } = error;
let key = 'errors.default';
if (response && this.i18n.te(`errors.${response.status}`)) {
key = `errors.${response.status}`;
} else if (error.message === 'Network Error') {
key = 'errors.network-error';
} else {
// TODO log unhandled errors
}
this.toast.error(this.i18n.t(key));
}
}
axios-resource
Simple axios resource class to easily interact with a REST endpoint.
Define a resource class. Here, I added onError and onFetchError as examples for your use-case.
import Resource from 'axios-resource';
export default class UserResource extends Resource {
static URL = 'user/{id}';
// This calls `sync` in the background
fetch() {
return super.fetch.apply(this, arguments)
.catch(err => this.onFetchError(err));
}
onFetchError(err) {
// An error occurred while fetching this resource.
}
onError(err) {
// An error occurred with this resource
}
// called for every actions (fetch, create, patch, delete)
sync() {
return super.sync.apply(this, arguments)
.catch((err) => this.onError(err))
}
}
Then, in api.js, create an instance.
import UserResource from './user';
const user = new UserResource();
// GET https://example.com/api/user/me
user.fetch('me')
.then(({ data }) => {
console.log('User data:', data);
});
The error can be dealt with at every step.
in the onFetchError of this specific resource
in the onError of this resource
in a middleware for the app.
You should add axios interceptors:
Axios Interceptors
You can intercept requests or responses before they are handled by
then or catch.
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
Those can (should) be in your Api.js.
I have separated my api call into three layers. The component, the repository, and the apihelper. I want the logic for refresh_tokens to be in apihelper.js. When I do this it seems like the apihelper runs again after getting the 401 response status but it never passes the data back up to the component. I know I could put the logic to rerun it in the component but that seems like it will end up being a lot of duplicate code as I add more calls. I feel like it's probably caused by my shallow understanding of javascript promises but I'm a javascript beginner.
Component
<script>
import breweryrepository from '#/repository/breweryrepository.js'
export default {
mounted() {
this._getTotalBreweries();
},
methods: {
_getTotalBreweries() {
breweryrepository.getTotalBreweries()
.then((response) => {
if(response.data)
{
this.totalNumberOfBreweries = response.data.totalBreweries;
}
})
}
},
data () {
return {
totalNumberOfBreweries: ''
}
}
}
</script>
Repository
import apihelper from '#/helpers/ApiHelper.js';
export default {
getTotalBreweries() {
return new Promise(function(resolve, reject) {
resolve(apihelper.apiCall('/brewery/totalnumber'));
});
}
}
Apihelper
import axios from 'axios';
var querystring = require('querystring');
import { store } from '../store/store.js';
import auth from '#/auth/auth.js'
export default {
apiCall(url) {
return axios.get(store.state.baseUrl + url, { 'headers': auth.getAuthHeader() })
.catch((error) => {
if(error.response.status == 401)
{
console.log("401 error, running refresh and apicall again");
auth.refreshToken();
this.apiCall(url);
}
})
}
}
Aaaaand I wasn't returning the call.
return this.apiCall(url);
Works now
I understand using observable I can execute a method when the request is completed, but how can i wait till a http get is completed and return the response using in ng2 http?
getAllUser(): Array<UserDTO> {
this.value = new Array<UserDTO>();
this.http.get("MY_URL")
.map(res => res.json())
.subscribe(
data => this.value = data,
err => console.log(err),
() => console.log("Completed")
);
return this.value;
}
the "value" will is null when its returned because get is async..
your service class: /project/app/services/sampleservice.ts
#Injectable()
export class SampleService {
constructor(private http: Http) {
}
private createAuthorizationHeader() {
return new Headers({'Authorization': 'Basic ZXBossffDFC++=='});
}
getAll(): Observable<any[]> {
const url='';
const active = 'status/active';
const header = { headers: this.createAuthorizationHeader() };
return this.http.get(url + active, header)
.map(
res => {
return res.json();
});
}
}
your component: /project/app/components/samplecomponent.ts
export class SampleComponent implements OnInit {
constructor(private sampleservice: SampleService) {
}
ngOnInit() {
this.dataset();
}
dataset(){
this.sampleservice.getAll().subscribe(
(res) => {
// map Your response with model class
// do Stuff Here or create method
this.create(res);
},
(err) => { }
);
}
create(data){
// do Your Stuff Here
}
}
By looking at the angular source (https://github.com/angular/angular/blob/master/packages/http/src/backends/xhr_backend.ts#L46), it is apparent that the async attribute of the XMLHttpRequest is not getting used. The third parameter of XMLHttpRequest needs to be set to "false" for synchronous requests.
Please find code for your problem
Below is component and service file.And Code is Working fine for synchornize
import { Component, OnInit } from '#angular/core';
import { LoginserviceService } from '../loginservice.service';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
model:any={};
constructor(private service : LoginserviceService) {
}
ngOnInit() {
}
save() {
this.service.callService(this.model.userName,this.model.passWord).
subscribe(
success => {
if(success) {
console.log("login Successfully done---------------------------- -");
this.model.success = "Login Successfully done";
}},
error => console.log("login did not work!")
);
}
}
Below is service file..
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { UserData } from './UserData';
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/toPromise'
import {Observable} from 'rxjs/Rx'
#Injectable()
export class LoginserviceService {
userData = new UserData('','');
constructor(private http:Http) { }
callService(username:string,passwrod:string):Observable<boolean> {
var flag : boolean;
return (this.http.get('http://localhost:4200/data.json').
map(response => response.json())).
map(data => {
this.userData = data;
return this.loginAuthentication(username,passwrod);
});
}
loginAuthentication(username:string,passwrod:string):boolean{
if(username==this.userData.username && passwrod==this.userData.password){
console.log("Authentication successfully")
return true;
}else{
return false;
}
}
}
Another solution would be to implement a priority queue of sort.
From what I understand http requests do not get executed until you add subscribers. Therefore, you can do something like this:
Observable<Response> observable = http.get("/api/path", new RequestOptions({}));
requestPriorityQueue.add(HttpPriorityQueue.PRIORITY_HIGHEST, observable,
successResponse => { /* Handle code */ },
errorResponse => { /* Handle error */ });
This assumes that requestPriorityQueue is a service injected into your component. The priority queue would store entries in an array in the following format:
Array<{
observable: Observable<Response>,
successCallback: Function,
errorCallback: Function
}>
You would have to decide how the elements are added to your array. Finally, the following will happen in the background:
// HttpPriorityQueue#processQueue() called at a set interval to automatically process queue entries
The processQueue method would do something like this:
protected processQueue() {
if (this.queueIsBusy()) {
return;
}
let entry: {} = getNextEntry();
let observable: Observable<Response> = entry.observable;
this.setQueueToBusy(); // Sets queue to busy and triggers an internal request timeout counter.
observable.subscribe()
.map(response => {
this.setQueueToReady();
entry.successCallback(response);
})
.catch(error => {
this.setQueueToReady();
entry.errorCallback(error);
});
}
If you are able to add new dependencies you could try using the following NPM package: async-priority-queue
I looked and I couldn't find any way to make an HTTP call sync instead of async.
So the only way around this: wrap your call in a while loop with a flag. Don't let the code continue until that flag has "continue" value.
Pseudo code as follows:
let letsContinue = false;
//Call your Async Function
this.myAsyncFunc().subscribe(data => {
letsContinue = true;
};
while (!letsContinue) {
console.log('... log flooding.. while we wait..a setimeout might be better');
}
as you see, first callback waiting for a data from request and
there you can go on with your logic (or use the third one)
example:
.. subscribe( data => {
this.value = data;
doSomeOperation;
},
error => console.log(error),
() => {console.log("Completed");
or do operations here..;
}
});
How about to use $.ajax(of jQuery) or XMLHttpRequest.
It can use as asynchornize.
You should not try to make http calls behave synchronously. Never a good idea.
Coming to your getAllUser implementation it should return an observable from the function and the calling code should subscribe instead of you creating a subscription inside the method itself.
Something like
getAllUser(): Observable<UserDTO> {
return this.http.get("MY_URL")
.map(res => res.json());
}
In you calling code, you should subscribe and do whatever you want.