How to use Sinon.js FakeXMLHttpRequest with superagent? - javascript

I am trying to test one of the React Flux actions that makes a request to the server.
// AppActions.js
fetchMovies(date) {
this.dispatch(date);
request
.get('/api/movies')
.query(date)
.end((err, res) => {
if (!res.ok) {
this.actions.fetchMoviesFail(res.body);
} else {
this.actions.fetchMoviesSuccess(res.body);
}
});
}
In my Flux store tests I have something like the following:
// AppStore-test.js
it ('should successfully handle fetchMovies', () => {
var callback = sinon.spy();
var date = {
startDate: moment('2015-04-01').format('YYYY-MM-DD'),
endDate: moment('2015-04-15').format('YYYY-MM-DD')
};
AppActions.fetchMovies(date, callback);
requests[0].respond(200, { 'Content-Type': 'application/json' },
'[{ "id": 12, "comment": "Hey there" }]');
expect(callback.calledWith([{id: 12, comment: "Hey there"}])).to.be.ok;
});
This obviously doesn't work because fetchMovies only takes one argument - date. It's my first time using sinon.js, so perhaps I am missing something really obvious?
How do I fake this asynchronous request and make it either succeed or fail, because right now no matter what I do, it never resolves the .end() promise.

You should use nock for this.
Nock intercepts all HTTP requests going from your code and then respons according to how you've set it up.
This way you can keep your mocks and spys down to a minimum and treat request as just another implementation detail.
The nock scope you create responds to certain assertions, so you can expect that it was done, faulty or similar.
An example;
var nockScope = nock('http://localhost:8000').get('/api/movies').reply(200, 'OK')
expect(nockScope.isDone()).to.be.ok()

Related

React concurrent API calls

So, my current code looks something like this:
component:
requestDescriptions(params, bothGender) {
if (bothGender) {
// men request
params.gender = 'men';
this.props.getDescriptions(params);
// women request
params.gender = 'women';
this.props.getDescriptions(params);
} else {
this.props.getDescriptions(params);
}
}
and my action looks like this:
export function getDescriptions(params = { brand: null, category: null, gender: null }) {
return (dispatch) => {
dispatch(requestDescription());
return request
.get(`${getApiUrl()}plp?${QueryString.stringify(params)}`)
.end((err, res) => {
if (err && res.statusCode !== 404) {
...
}
if (res.statusCode === 404) {
// HERE:
console.log(params.gender);
return dispatch(receiveEmptyDescription(params));
}
return dispatch(receiveDescription(res.body));
});
};
}
So up until now, I hadn't noticed the error, but just did when I began testing a scenario where both API calls return a 404.
The error being that having the concurrent calls to the same endpoint is overwriting my params. When the API returns a 200, I could not see the problem. But now when I'm testing returning a 404 I can clearly see the problem.
The problem im getting lies here:
this.props.getDescriptions(params);
params.gender = 'women';
If I check the console log inside my action ( //HERE ) , on both cases params.gender displays 'women' even though the first time around its supposed to be 'men'
I guess this can be fixed using a promise?
Let me know if I'm not clear enough.
The problem isn't really that you're making parallel requests or that you're getting an HTTP2XX or an HTTP4XX.
The reason you are always seeing gender equal to 'women' is that you're using the same object instance for both requests, i.e. params.
What is happening is that you set params.gender to be 'men', fire of the first request, set params.gender to be 'women', fire of the second request.
All the steps above happen synchronously, before any request is completed.
This means that long before any request is finished, params.gender is already equal to 'women'.
Just send a different object to each action.
requestDescriptions(params, bothGender) {
if (bothGender) {
// men request
this.props.getDescriptions({ ...params, gender: 'men' });
// women request
this.props.getDescriptions({ ...params, gender: 'women' });
} else {
this.props.getDescriptions(params);
}
}
Since you're already using Redux, you should be aware that mutation is baaaad 😊

Ember Understand execution flow between route/controller

I have a "box" route/controller as below;
export default Ember.Controller.extend({
initialized: false,
type: 'P',
status: 'done',
layouts: null,
toggleFltr: null,
gridVals: Ember.computed.alias('model.gridParas'),
gridParas: Ember.computed('myServerPars', function() {
this.set('gridVals.serverParas', this.get('myServerPars'));
this.filterCols();
if (!this.get('initialized')) {
this.toggleProperty('initialized');
} else {
Ember.run.scheduleOnce('afterRender', this, this.refreshBox);
}
return this.get('gridVals');
}),
filterCols: function()
{
this.set('gridVals.layout', this.get('layouts')[this.get('type')]);
},
myServerPars: function() {
// Code to set serverParas
return serverParas;
}.property('type', 'status', 'toggleFltr'),
refreshBox: function(){
// Code to trigger refresh grid
}
});
My route looks like;
export default Ember.Route.extend({
selectedRows: '',
selectedCount: 0,
rawResponse: {},
model: function() {
var compObj = {};
compObj.gridParas = this.get('gridParas');
return compObj;
},
activate: function() {
var self = this;
self.layouts = {};
var someData = {attr1:"I"};
var promise = this.doPost(someData, '/myService1', false); // Sync request (Is there some way I can make this work using "async")
promise.then(function(response) {
// Code to use response & set self.layouts
self.controllerFor(self.routeName).set('layouts', self.layouts);
});
},
gridParas: function() {
var self = this;
var returnObj = {};
returnObj.url = '/myService2';
returnObj.beforeLoadComplete = function(records) {
// Code to use response & set records
return records;
};
return returnObj;
}.property(),
actions: {
}
});
My template looks like
{{my-grid params=this.gridParas elementId='myGrid'}}
My doPost method looks like below;
doPost: function(postData, requestUrl, isAsync){
requestUrl = this.getURL(requestUrl);
isAsync = (isAsync == undefined) ? true : isAsync;
var promise = new Ember.RSVP.Promise(function(resolve, reject) {
return $.ajax({
// settings
}).success(resolve).error(reject);
});
return promise;
}
Given the above setup, I wanted to understand the flow/sequence of execution (i.e. for the different hooks).
I was trying to debug and it kept hopping from one class to another.
Also, 2 specific questions;
I was expecting the "activate" hook to be fired initially, but found out that is not the case. It first executes the "gridParas" hook
i.e. before the "activate" hook. Is it because of "gridParas"
specified in the template ?
When I do this.doPost() for /myService1, it has to be a "sync" request, else the flow of execution changes and I get an error.
Actually I want the code inside filterCols() controller i.e.
this.set('gridVals.layout', this.get('layouts')[this.get('type')]) to
be executed only after the response has been received from
/myService1. However, as of now, I have to use a "sync" request to do
that, otherwise with "async", the execution moves to filterCols() and
since I do not have the response yet, it throws an error.
Just to add, I am using Ember v 2.0
activate() on the route is triggered after the beforeModel, model and afterModel hooks... because those 3 hooks are considered the "validation phase" (which determines if the route will resolve at all). To be clear, this route hook has nothing to do with using gridParas in your template... it has everything to do with callling get('gridParas') within your model hook.
It is not clear to me where doPost() is connected to the rest of your code... however because it is returning a promise object you can tack on a then() which will allow you to essentially wait for the promise response and then use it in the rest of your code.
Simple Example:
this.doPost().then((theResponse) => {
this.doSomethingWith(theResponse);
});
If you can simplify your question to be more clear and concise, i may be able to provide more info
Generally at this level you should explain what you want to archive, and not just ask how it works, because I think you fight a lot against the framework!
But I take this out of your comment.
First, you don't need your doPost method! jQuerys $.ajax returns a thenable, that can be resolved to a Promise with Ember.RSVP.resolve!
Next: If you want to fetch data before actually rendering anything you should do this in the model hook!
I'm not sure if you want to fetch /service1, and then with the response you build a request to /service2, or if you can fetch both services independently and then show your data (your grid?) with the data of both services. So here are both ways:
If you can fetch both services independently do this in your routes model hook:
return Ember.RSVP.hash({
service1: Ember.RSVP.resolve($.ajax(/*your request to /service1 with all data and params, may use query-params!*/).then(data => {
return data; // extract the data you need, may transform the response, etc.
},
service2: Ember.RSVP.resolve($.ajax(/*your request to /service2 with all data and params, may use query-params!*/).then(data => {
return data; // extract the data you need, may transform the response, etc.
},
});
If you need the response of /service1 to fetch /service2 just do this in your model hook:
return Ember.RSVP.resolve($.ajax(/*/service1*/)).then(service1 => {
return Ember.RSVP.resolve($.ajax(/*/service2*/)).then(service2 => {
return {
service1,
service2
}; // this object will then be available as `model` on your controller
});
});
If this does not help you (and I really think this should fix your problems) please describe your Problem.

Why doesn't the Reflux.js listenAndPromise helper work?

I'm using qwest to query my endpoint as shown below, the onGetResourceCompleted handler fires as expected but data is undefined. Why?
var Actions = Reflux.createActions({
'getResource': { asyncResult: true }
});
Actions.getResource.listenAndPromise(function (id) {
return qwest.get('http://localhost:8000/my-data/'+id, null, { withCredentials: true });
});
var MyStore = Reflux.createStore({
listenables: Actions,
init: function () {
Actions.getResource('');
},
onGetResourceCompleted: function (data) {
console.log('OK', data); // Get's called but data is undefined. Why?
}
});
I can see the data loads correctly by looking at dev tools as well as calling qwest in isolation by simply doing:
qwest.get('http://localhost:8000/my-data/'+id, null, { withCredentials: true }).then(function(data) {
console.log('OK', data);
});
Also doing the following works:
ServiceActions.getResource.listen(function (id) {
ServiceActions.getResource.promise(
qwest.get('http://localhost:8000/my-data/'+id, null, { withCredentials: true })
);
});
I've put some comments on the cause of this "confirmed bug" in the original issue you opened at github.com/spoike/refluxjs.
So, though you are using the reflux features the way they are intended, and they're definitely creating a race condition without even returning the race results, I think you're in luck. It turns out the two particular features you're using in this combination with this type of request is a bit redundant when you already have a promise available. I'd recommend you just drop the onGetRequestCompleted handler entirely, and handle completion using the standard promise ways of handling resolved promises, which honestly will give you more flexibility anyways.
For example:
var MyStore = Reflux.createStore({
listenables: Actions,
init: function () {
Actions.getResource('')
.then() <-- this eliminates the need for onGetResourceCompleted
.catch() <-- or this instead/in addition
.finally() <-- or this instead/in additon
},
// no more onGetResourceCompleted
});

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

I have an angular service called requestNotificationChannel:
app.factory("requestNotificationChannel", function($rootScope) {
var _DELETE_MESSAGE_ = "_DELETE_MESSAGE_";
function deleteMessage(id, index) {
$rootScope.$broadcast(_DELETE_MESSAGE_, { id: id, index: index });
};
return {
deleteMessage: deleteMessage
};
});
I am trying to unit test this service using jasmine:
"use strict";
describe("Request Notification Channel", function() {
var requestNotificationChannel, rootScope, scope;
beforeEach(function(_requestNotificationChannel_) {
module("messageAppModule");
inject(function($injector, _requestNotificationChannel_) {
rootScope = $injector.get("$rootScope");
scope = rootScope.$new();
requestNotificationChannel = _requestNotificationChannel_;
})
spyOn(rootScope, '$broadcast');
});
it("should broadcast delete message notification", function(done) {
requestNotificationChannel.deleteMessage(1, 4);
expect(rootScope.$broadcast).toHaveBeenCalledWith("_DELETE_MESSAGE_", { id: 1, index: 4 });
done();
});
});
I read about the Asynchronous Support in Jasmine, but as I am rather new to unit testing with javascript couldn't make it work.
I am receiving an error :
Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL
and my test is taking too long to execute (about 5s).
Can somebody help me providing working example of my code with some explanation?
Having an argument in your it function (done in the code below) will cause Jasmine to attempt an async call.
//this block signature will trigger async behavior.
it("should work", function(done){
//...
});
//this block signature will run synchronously
it("should work", function(){
//...
});
It doesn't make a difference what the done argument is named, its existence is all that matters. I ran into this issue from too much copy/pasta.
The Jasmine Asynchronous Support docs note that argument (named done above) is a callback that can be called to let Jasmine know when an asynchronous function is complete. If you never call it, Jasmine will never know your test is done and will eventually timeout.
Even for async tests, there is a timeout that goes off in this cases, You can work around this error by increasing the value for the limit timeout to evaluate an async Jasmine callback
describe('Helper', function () {
var originalTimeout;
beforeEach(function() {
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000000;
});
afterEach(function() {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});
it('Template advance', function(doneFn) {
$.ajax({
url: 'public/your-end-point.mock.json',
dataType: 'json',
success: function (data, response) {
// Here your expected using data
expect(1).toBe(1)
doneFn();
},
error: function (data, response) {
// Here your expected using data
expect(1).toBe(1)
doneFn();
}
});
});
});
Source: http://jasmine.github.io/2.0/introduction.html#section-42
This error can also be caused by leaving out inject when initializing a service/factory or whatever. For example, it can be thrown by doing this:
var service;
beforeEach(function(_TestService_) {
service = _TestService_;
});
To fix it just wrap the function with inject to properly retrieve the service:
var service;
beforeEach(inject(function(_TestService_) {
service = _TestService_;
}));
import { fakeAsync, ComponentFixture, TestBed } from '#angular/core/testing';
use fakeAsync
beforeEach(fakeAsync (() => {
//your code
}));
describe('Intilalize', () => {
it('should have a defined component', fakeAsync(() => {
createComponent();
expect(_AddComponent.ngOnInit).toBeDefined();
}));
});
You can use karma-jasmine plugin to set the default time out interval globally.
Add this config in karma.conf.js
module.exports = function(config) {
config.set({
client: {
jasmine: {
timeoutInterval: 10000
}
}
})
}
This error started out of the blue for me, on a test that had always worked. I couldn't find any suggestions that helped until I noticed my Macbook was running sluggishly. I noticed the CPU was pegged by another process, which I killed. The Jasmine async error disappeared and my tests are fine once again.
Don't ask me why, I don't know. But in my circumstance it seemed to be a lack of system resources at fault.
This is more of an observation than an answer, but it may help others who were as frustrated as I was.
I kept getting this error from two tests in my suite. I thought I had simply broken the tests with the refactoring I was doing, so after backing out changes didn't work, I reverted to earlier code, twice (two revisions back) thinking it'd get rid of the error. Doing so changed nothing. I chased my tail all day yesterday, and part of this morning without resolving the issue.
I got frustrated and checked out the code onto a laptop this morning. Ran the entire test suite (about 180 tests), no errors. So the errors were never in the code or tests. Went back to my dev box and rebooted it to clear anything in memory that might have been causing the issue. No change, same errors on the same two tests. So I deleted the directory from my machine, and checked it back out. Voila! No errors.
No idea what caused it, or how to fix it, but deleting the working directory and checking it back out fixed whatever it was.
Hope this helps someone.
You also get this error when expecting something in the beforeAll function!
describe('...', function () {
beforeAll(function () {
...
expect(element(by.css('[id="title"]')).isDisplayed()).toBe(true);
});
it('should successfully ...', function () {
}
}
Don't use done, just leave the function call empty.
It looks like the test is waiting for some callback that never comes. It's likely because the test is not executed with asynchronous behavior.
First, see if just using fakeAsync in your "it" scenario:
it('should do something', fakeAsync(() => {
You can also use flush() to wait for the microTask queue to finish or tick() to wait a specified amount of time.
In my case, this error was caused by improper use of "fixture.detectChanges()" It seems this method is an event listener (async) which will only respond a callback when changes are detected. If no changes are detected it will not invoke the callback, resulting in a timeout error. Hope this helps :)
Works after removing the scope reference and the function arguments:
"use strict";
describe("Request Notification Channel", function() {
var requestNotificationChannel, rootScope;
beforeEach(function() {
module("messageAppModule");
inject(function($injector, _requestNotificationChannel_) {
rootScope = $injector.get("$rootScope");
requestNotificationChannel = _requestNotificationChannel_;
})
spyOn(rootScope, "$broadcast");
});
it("should broadcast delete message notification with provided params", function() {
requestNotificationChannel.deleteMessage(1, 4);
expect(rootScope.$broadcast).toHaveBeenCalledWith("_DELETE_MESSAGE_", { id: 1, index: 4} );
});
});
What I did was: Added/Updated the following code:
framework: 'jasmine',
jasmineNodeOpts:
{
// Jasmine default timeout
defaultTimeoutInterval: 60000,
expectationResultHandler(passed, assertion)
{
// do something
},
}
As noted by #mastablasta, but also to add that if you call the 'done' argument or rather name it completed you just call the callback completed() in your test when it's done.
// this block signature will trigger async behavior.
it("should work", function(done){
// do stuff and then call done...
done();
});
// this block signature will run synchronously
it("should work", function(){
//...
});
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100000;
Keeping this in the block solved my issue.
it('', () => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100000;
});
Instead of
beforeEach(() => {..
use
beforeEach(fakeAsync(() => {..
In my case, a timeout was cause because of a failed injection of a service with providedIn: 'root'. It's not clear why injection failed, nor why there was no early error if there is apparently no instance of provider available.
I was able to work around it by manually providing a value:
TestBed.configureTestingModule({
declarations: [
// ...
],
imports: [
// ...
],
providers: [
// ...
{ provide: MyService, useValue: { /* ... */ } },
]
}).compileComponents();
I have caught the same error because I used the setTimeout function in the component. Example:
ngOnInit(): void {
this.changeState();
}
private changeState(): void {
setTimeout(() => this.state = StateEnum.IN_PROGRESS, 10000);
}
When I changed the timeout from 10000ms to 0 or less than 5000ms (DEFAULT_TIMEOUT_INTERVAL), all tests were passed.
In my case, I was not returning the value from the spy method, hence facing error,
mainMethod(args): Observable<something>{
return nestedMethod().pipe();
}
Your Test should like below,
it('your test case', (done: DoneFn) => {
const testData = {}; // Your data
spyOn(service, 'nestedMethod').and.returnValue(of(testData));
const obxValue = service.mainMethod('your args');
obxValue.pipe(first()).subscribe((data) => {
expect(data).not.toBeUndefined();
done();
});
});
If you have an argument (done) in the it function try to remove it as well it's call within the function itself:
it("should broadcast delete message notification", function(/*done -> YOU SHOULD REMOVE IT */) {
requestNotificationChannel.deleteMessage(1, 4);
expect(rootScope.$broadcast).toHaveBeenCalledWith("_DELETE_MESSAGE_", { id: 1, index: 4 });
// done(); -> YOU SHOULD REMOVE IT
});

How do I mock a 'timeout' or 'failure' response using Sinon / Qunit?

I've had no problems sorting out mocking the success condition, but cannot seem to fathom how to mock the failure/timeout conditions when using Sinon and Qunit to test and ajax function:
My set up is this:
$(document).ready( function() {
module( "myTests", {
setup: function() {
xhr = sinon.sandbox.useFakeXMLHttpRequest();
xhr.requests = [];
xhr.onCreate = function (request) {
xhr.requests.push(request);
};
myObj = new MyObj("#elemSelector");
},
teardown: function() {
myObj.destroy();
xhr.restore();
}
});
});
and my success case test, running happily and receiving/passing through the received data to the success method is this:
test("The data fetch method reacts correctly to receiving data",
function () {
sinon.spy(MyObject.prototype, "ajaxSuccess");
MyObject.prototype.fetchData();
//check a call got heard
equal(1, xhr.requests.length);
//return a success method for that obj
xhr.requests[0].respond(200, {
"Content-Type": "application/json"
},
'[{ "responseData": "some test data" }]'
);
//check the correct success method was called
ok(MyObj.prototype.ajaxSuccess.calledOnce);
MyObj.prototype.ajaxSuccess.restore();
}
);
However, I cannot work out what I should be putting instead of this:
xhr.requests[0].respond(200, { "Content-Type": "application/json" },
'[{ "responseData": "some test data" }]');
to make my ajax call handler hear a failure or timeout method? The only thing I could think to try was this:
xhr.requests[0].respond(408);
But it doesn't work.
What am I doing wrong or what have I misunderstood? All help much appreciated :)
For the timeout, sinon’s fake timers could help. Using them you wouldn’t need to set the timeout to 1ms. As for the failures, your approach looks correct to me. Can you give us more code, especially the failure handler?
Doing something like this
requests[0].respond(
404,
{
'Content-Type': 'text/plain',
'Content-Length': 14
},
'File not found'
);
works to trigger the 'error' callback in jQuery AJAX requests.
As for the timouts, you can use sinons fake clock like this:
test('timeout-test', function() {
var clock = sinon.useFakeTimers();
var errorCallback = sinon.spy();
jQuery.ajax({
url: '/foobar.php',
data: 'some data',
error: errorCallback,
timeout: 20000 // 20 seconds
});
// Advance 19 seconds in time
clock.tick(19000);
strictEqual(errorCallback.callCount, 0, 'error callback was not called before timeout');
// Advance another 2 seconds in time
clock.tick(2000);
strictEqual(errorCallback.callCount, 1, 'error callback was called once after timeout');
});
The main idea that I would use is to wrap everything related to the request inside "another function" that returns a promise.
Then in the test, when I mock the "another function" I just return a Promise.reject({}).
If some endpoint is going to give me a timeout, that is equivalent to a failed promise.
Set a timeout on your $.ajax() call and use Sinon fake timers to move the clock ahead before responding.

Categories

Resources