React ES6 with chai/sinon: Unit testing XHR in component - reactjs

I have a React utility component that reads the contents of a URL:
'use strict';
export class ReadURL {
getContent = (url) => {
return new Promise((resolve, reject) => {
console.log('Promise')
let xhr = new XMLHttpRequest();
xhr.open("GET", url, false);
xhr.onreadystatechange = () => {
console.log('onreadystatechange', xhr.readyState)
if (xhr.readyState === 4) {
if (xhr.status === 200 || xhr.status == 0) {
console.log('200')
var allText = xhr.responseText;
resolve(allText);
} else {
reject('ajax error:' + xhr.status + ' ' + xhr.responseText);
}
}
};
xhr.send(null);
});
};
}
I have been trying to use Sinon's useFakeXMLHttpRequest() to stub the xhr, but no matter how I try, I can't get it to actually process - It currently passes with a false positive, without ever receiving onreadystatechange event.
I've tried with XHR and Axios packages as well as native XMLHttpRequest, with the request wrapped in a promise and not, a whole bunch of different tacks, and read untold blog posts, docs and SO questions and I'm losing the will to live... The component itself works perfectly.
I've managed to get tests working with promises and with stubbed module dependancies, but this has me stumped.
This is the test:
import chai, { expect } from 'chai';
import sinon, { spy } from 'sinon';
import {ReadURL} from './ReadURL';
describe('ReadURL', () => {
beforeEach(function() {
this.xhr = sinon.useFakeXMLHttpRequest();
this.requests = [];
this.xhr.onCreate = (xhr) => {
console.log('xhr created', xhr)
this.requests.push(xhr);
};
this.response = 'Response not set';
});
afterEach(function() {
this.xhr.restore();
this.response = 'Response not set';
});
it('should get file content from an xhr request', () => {
const readURL = new ReadURL(),
url = 'http://dummy.com/file.js',
urlContent = `<awe.DisplayCode
htmlSelector={'.awe-login'}
jsxFile={'/src/js/components/AncoaAwe.js'}
jsxTag={'awe.Login'}
componentFile={'/src/js/components/Login/Login.js'}
/>`;
readURL.getContent(url).then((response) =>{
console.log('ReadURL-test response', response)
expect(response).to.equal(urlContent);
});
window.setTimeout(() => {
console.log('ReadURL-test trigger response')
this.requests[0].respond(200,
{
'Content-Type': 'application/json'
},
urlContent
)
, 10});
});
});
The console.log('xhr created', xhr) is triggered, and output confirms that it's a sinon useFakeXMLHttpRequest request.
I have created a repo of the app with the bare minimum required to see the components functions:
https://github.com/DisasterMan78/awe-testcase
I haven't got a sample online currently, as I don't know of any online sandboxes that run tests. If I can find a service for that I'll try to add a proof of failed concept.
Help me Obi Wan-Kenobi. You're my only hope!

Well, that was fun. I got back to it today after working on an older project, but it still took my far too long. Stupid, small syntactic errors. Of course...
There were two critical errors.
Firstly, in order for the promise to complete, done needs to be passed as a parameter to the function of the test's it() function:
it('should get file content from an xhr request', (done) => {
...
}
Now we can complete the promise with done:
...
readURL.getContent(url)
.then((response) => {
expect(response).to.equal(this.response.content);
done();
})
.catch((error) => {
console.log(error);
done();
});
...
Somehow none of the documentation or articles I read seemed to flag this, but few of them were also dealing with promises, and my ExtractTagFromURL test, which also relies on promises did not require this, but I have confirmed it is absolutely critical to this test.
With the promise working properly, timeouts are not required around the respond() call:
...
this.requests[0].respond(this.response.status,
{
'Content-Type': this.response.contentType
},
this.response.content
);
...
And finally, my largest cock up, in order for the values of this for this.requests and the other shared properties to be properly set and read, the function parameter of beforeEach() and afterEach() need to use arrow syntax:
...
beforeEach(() => {
...
}
...
The reduced test case on Github has been updated, passes and can be cloned from https://github.com/DisasterMan78/awe-testcase
I guess this makes me Obi Wan-Kenobe, huh?
Feel free to ask for clarification if needed!

Related

Angular Promise Conditional

My Angular v1.x directive calls a function when a button is clicked and depending on the input I set some variables:
$scope.offerRespond = function() {
if (offer.i.offerResponse == 1) {
// set some variables
}
else if (offer.i.offerResponse == 0) {
$http({method: 'GET', url: frontbaseurl+'/offer_types/' + id + '.json'})
.then(function successCallback(response) {
$scope.reason = response.data.data.name;
}, function errorCallback() {
$scope.reason = "Unknown";
}
);
$http.post(frontbaseurl+'/purchases.json', JSON.stringify(dataObj))
.then(function(response){
// continue....
}
}
As can be seen, I make a GET request if the offerResponse == 0. This seems risky because the code continues to execute without before a response from the request is received. I know the approach is to use a 'promise' but how does that apply in this case if the GET request may not be made?
The problem as I see it is if I add a promise and the offerResponse == 1 the promise can't be fulfilled since the GET request would not have been made.
Any suggestions please?
I've had to do something similar in our angular 1.x app, using Typescript, like this:
public myFunc = async (id): Promise<void> => {
return new Promise<void>(async (resolve, reject) => {
if (this.offer.i.offerResponse === 1) {
// set some variables
resolve();
}
else if (this.offer.i.offerResponse === 0) {
try {
const response = await services.getOfferTypes(id);
this.reason = response.data.data.name;
resolve();
} catch (errorResponse) {
this.reason = "Unknown";
reject();
}
}
});
}
Notice I "encapsulated" your http request in a service function. I like separating api calls into their own service, to keep things clean and easy to read. This code is untested, but if I remember correctly it should work something like this.

Calling $http.delete but nothing happens

I'm actually working on a webclient calling a REST service.
After my last question, the GET request is working now.
Now i want o implement a DEL request using angulars delete method.
In the following example is my service request implemented.
function ItemsService($http) {
this.$http = $http;
}
ItemsService.prototype = {
deleteFoo: function (id) {
this.$http.delete('http://localhost:3001/posts/' + id)
.then(function successCallback(response) {
console.log("DEL send...");
console.log(response.data);
}, function errorCallback(response) {
console.log('Error');
});
}
}
module.exports = {
ItemsService: ItemsService
}
I added a button on the webpage with ng-click="$ctrl.deleteMe()".
The controller looks like the following example:
function Foo(ItemsService) {
this.itemsService = ItemsService;
}
Foo.prototype = {
deleteMe: function () {
console.log('delete now');
this.itemsService.deleteFoo(1).then(
response => {
console.log('gelöscht? ' + response);
}
);
}
};
If i now click on the button, nothing happens. In the network trace log in the dev tools in the browser i can't see a DEL request.
To test this REST service request, i run the JSON Server tool on port 3001.
I testet the availability of the server with SOAPUI, it works, i see all the requests in the console.
But no request from my test webpage.
Can anyone help me?
You need to return
return this.$http.delete('http://localhost:3001/posts/' + id)
.then(function successCallback(response) {
console.log("DEL send...");
console.log(response.data);
}, function errorCallback(response) {
console.log('Error');
});

Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL

I am trying to test my Ionic app with Jasmine. This is my test suit.
beforeEach(() => {
auth = new Authentication(<any>new HttpMock(), <any>new StorageMock())
user = new MockUser();
header = new Headers({'uid': '1'});
resp = new Response( new ResponseOptions({body: {name: user.name }, headers: header}))
});
it('facebok login ',(done)=>{
spyOn(auth.http,'post').and.returnValue(HttpMock.returnValue(resp));
spyOn(Facebook,'login').and.returnValue(Promise.resolve({authResponse: {accessToken: 1}}))
auth.facebookLogin().then((res)=>{
expect(auth.http.post.calls.argsFor(0)).toEqual([CONFIG.url.facebookUrl,{}])
expect(res.authResponse.accessToken).toEqual(1);
done();
},(err)=>{
done();
});
});
My HttpMock class to mock http calls looks like this.
export class HttpMock {
static returnValue(data){
return Observable.create((observer) => {
observer.next(data);
})
}
}
The relevant part in the service I am testing is,
facebookLogin(): Promise<any>{
let permissions = ["public_profile","email"];
return Facebook.login(permissions)
.then( (response) => {
let token = { access_token: response.authResponse.accessToken };
return this.login( token ,'facebookUrl').toPromise();
}).catch( this.handleError);
login(data , urlKey): Observable<any>{
return this.http.post(CONFIG.url[urlKey], data)
.map( (res: Response) => this.saveUserInfo(res) ).catch( this.handleError)
}
saveUserInfo(res: Response): Response{
let userInfo = this.getUserInfo(res);
this.user = userInfo;
this.storage.set('user', userInfo);
return res;
}
The facebookLogin method goes like this. Access Facebook class login method which returns a promise. With information from the promise, I make http post request and save the returned data and then convert observable to promise with toPromise. In the test I spy on Facebook.login to return a resolving promise and spyOn http.post to return a successful observable. This is working fine in my app.But I am unable to run the test as it give the following error.
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
The code runs fine till the last point in http.post.map but then is not being run in the test. I think the problem is with the toPromise in the service.
Any kind of hep would be appreciated.
From my limited knowledge on Observable , I believe the problem with the approach was due to the fact that toPromise didnt get the value from observer.next(data). I assume subscription is necessary for that. The simple approach with Observable.of worked for me. You can import it from import 'rxjs/add/observable/of'

Testing $resource without mock

I'm trying to test the asynchronous $resource service by Angular. Firstly note that I don't want to mock $resource, I actually want to get the results and test them.
So basically my getCountries method just uses Angular's $resource to return an array of countries.
Method
getCountries(): any {
var countries = this.$resource(
this.apiEndpoint.baseUrl,
{
'get': {
method: 'GET', isArray: false
}
});
return countries;
}
In my spec I'm having trouble with where to run Jasmine's done() method. Please read comments in the code.
Spec
describe(module', (): void => {
var one;
var SelfRegistrationResourceBuilder;
beforeEach((): void => {
angular.mock.module(
'BuilderModule',
'ngResource'
);
inject(
['Builder', (_SelfRegistrationResourceBuilder _: ISelfRegistrationResourceBuilder ): void => {
SelfRegistrationResourceBuilder = _SelfRegistrationResourceBuilder_;
}]);
});
describe('getCountries', (): void => {
beforeEach((done) => { //done is a parameter in beforeEach
SelfRegistrationResourceBuilder.getCountries().get(
(value: any) => {
console.log("no error");
one = "aValue";
done(); //Do I have to call done() here
}, (error: any) => {
console.log("error");
one = "aValue";
//done(); What about here
})
//done(); ..or here
});
it("should...", (done) => {
expect(one).toBe("aValue");
//done(); and here?
});
});
});
So my problem seems to be that when I run my code I either get the error stated in the title of this problem or if I play around with which done() methods I call then my variable named "one" is undefined, thus implying that the success and error call backs never got called. I can also prove that the success and error callbacks never get called because their console.log()s don't ever print to the console.
Lastly, when I check the network section of the developer tools in my browser I don't see any requests to the server. Could someone please try to help me out here, I think I have most of it figured out except the calling of done().
Thanks
done is for async tests. If you test is not async don't use done e.g.:
// No done needed
it("should...", () => {
expect(one).toBe("aValue");
});
For async tests call it after the async operation is complete. So :
beforeEach((done) => { //done is a parameter in beforeEach
SelfRegistrationResourceBuilder.getCountries().get(
(value: any) => {
console.log("no error");
one = "aValue";
done(); // Yes call done
}, (error: any) => {
console.log("error");
one = "aValue";
// do not call done. You most likely want to throw to show this is an error case.
})
});

QUnit & sinon. Spying on a function called asynchronously

With Sinon, I'm trying to spy on an async function call from a function in my qunit test:
test("requestLiveCategoriesData should call parseCategoriesData", function(){
var spy = sinon.spy(this.liveCategoriesModel, 'parseCategoriesData');
this.liveCategoriesModel.requestLiveCategoriesData();
sinon.assert.calledOnce(spy);
});
The test fails (expected parseCategoriesData to be called once but was called 0 times) even though parseCategoriesData does indeed get called by the requestLiveCategoriesData - I know this because parseCategoriesData called is output to the console when I run the test in the browser
This is the code I'm testing (simplified for the sake of the question):
requestLiveCategoriesData: function () {
console.log('getting live categories');
try {
console.log("--- RETRIEVING LIVE CATEGORIES EVENTS ---");
liveCategoriesCall = new LiveEventRequest(eventObjRequest);
liveCategoriesCall.on('reset', this.parseCategoriesData, this); //the spied on function is called from here
liveCategoriesCall.fetch({
success: function (collection, resp, options) {
console.log('Live Categories Events Request complete.');
},
error: function(collection, resp) {
console.log("Error on Live Categories Events Request");
if (_.has(resp, 'statusText') && resp.statusText === "timeout") {
/* Timeout error handling */
console.log("Live Categories Events Request Timeout");
}
Conf.generalNetworkError();
},
complete: function (resp, textStatus) {
console.log("Live Categories Request teardown.");
if (liveCategoriesCall) { liveCategoriesCall.off('reset', that.parseCategoriesData, that); }
},
cache:false,
timeout: that.get('liveEventsTimeout')
});
} catch(err) {
console.log("ERROR: PROCESSING LIVE CATEGORIES");
console.log(err.message);
console.log(err.stack);
if (liveCategoriesCall) { liveCategoriesCall.off('reset', this.parseEventsData, this); }
this.set({
'lastRequest': (new Date()).getTime(),
'liveCategories': []
});
this.trigger("errorAPI", err.message);
}
},
parseCategoriesData: function (liveCategoriesCall) {
console.log('parseCategoriesData called');
},
Am I going about this the correct way?
You at least need to instruct QUnit to wait on the asynchronous response call using async().
Now when you've got that set up you need to figure out when you can call sinon.assert.calledOnce(spy);. It looks like there is currently no way to know when the LiveEventRequest has returned data.
If you have no way of modifying the current code using a setTimeout to wait a bit is your only (bad) option.
If you can change your code you should probably investigate if you can return a promise from the requestLiveCategoriesData call. Have the promise resolve when the data has arrived. Then you can wait on that promise before you do the Sinon check and follow that by a done() call like in the QUnit async documentation.
And while we're at it: You probably should use a sinon fakeserver or some other way to mock the results of the LiveEventRequest as well.

Resources