When testing a marionette compositeviewm in the onBeforeShow function, I am performing a collection.fetch with a success callback. However within the callback I then go on to clone my collection and attach events to it.
Now my issue is when testing this view, how would I target the various events/function calls I have set in the success callback of collection.fetch?
Is it possible or is it a case of not simply not being able to and code refactoring should take place?
If I set a sinon spy on checkEmptyState and then add a model to my collection created for the tests, the spy is not triggered, which makes sense as I have stubbed the fetch method for the collection in the beforeEach function in order to prevent any api calls. Which would be the best approach/set-up to enable testing the code within the success callback, if possible?
For example:
onBeforeShow: function () {
var that = this;
this.fetch = this.collection.fetch({
success: function (collection, response, options) {
if (!response.length ) {
that.addEmptyPostsMessage();
}
that.stopListening(that.collection);
//Change collection property and re-apply events
that.collection = that.collection.clone(that.options.filterAttr, that.options.isFiltered);
that._initialEvents();
that.collection.reset(that.collection.filterItems(that.collection.models, that.options.filterAttr), {reset: true});
that.listenTo(that.collection, 'update', that.checkEmptyState);
},
reset: false,
remove: false
});
}
For my test set-up:
beforeEach(function () {
this.server = sinon.fakeServer.create();
this.server.autoRespond = true;
this.collectionFetchStub = sinon.stub(My.Collection.Items.prototype, 'fetch', function () {
return: {
success: function () {console.log("this is never called!")}
}
});
My success callback in my stub is never reached.
You could make sure that you fetch stub executes it's success callback. Something like
var collection = [...],
response = 'ok';
this.collectionFetchStub = sinon.stub(My.Collection.Items.prototype, 'fetch', function (options) {
options.success(collection, response);
};
Or when using a sinon fakeserver instead of using autoRespond you should try
server.respondWith([200, { 'Content-Type': 'text/html', 'Content-Length': 2 }, collection_json_as_string]);
Make sure that the response is valid json.
Related
Node version: v12.18.3
Sails version (sails): 1.2.3
I am unable to stub a sails helper when performing unit tests. I have a helper that handles all the communication with a database. Moreover, I have an API, which uses this helper. In my tests, I am trying to stub the helper using sinon as such:
The API:
fn: async function (inputs, exits) {
// Stuff done here
// I need to stub this helper
let result = await sails.helpers.arangoQuery.with({
requestId: REQUEST_ID,
query: query,
queryParams: params
});
}
My test:
describe('Get Organization', () => {
it('Server Error - Simulates a failure in fetching the data from ArangoDB', (done) => {
sinon.stub(sails.helpers, 'arangoQuery').returns(null, {status: "success"});
supertest(sails.hooks.http.app)
.get('/organization')
//.expect(200)
.end((error, response) => {
return done()
}
})
})
When I run the test, I get the following error:
error: Error: cannot GET /organization (500)
at Response.toError (/opt/designhubz/organization-service/node_modules/superagent/lib/node/response.js:94:15)
at ResponseBase._setStatusProperties (/opt/designhubz/organization-service/node_modules/superagent/lib/response-base.js:123:16)
at new Response (/opt/designhubz/organization-service/node_modules/superagent/lib/node/response.js:41:8)
at Test.Request._emitResponse (/opt/designhubz/organization-service/node_modules/superagent/lib/node/index.js:752:20)
at /opt/designhubz/organization-service/node_modules/superagent/lib/node/index.js:916:38
at IncomingMessage.<anonymous> (/opt/designhubz/organization-service/node_modules/superagent/lib/node/parsers/json.js:19:7)
at IncomingMessage.emit (events.js:327:22)
at endReadableNT (_stream_readable.js:1220:12)
at processTicksAndRejections (internal/process/task_queues.js:84:21) {
status: 500,
text: '{}',
method: 'GET',
path: '/organization'
}
There are no documentations at all regarding this issue. Can anyone tell me how I can stub a helper?
Sails helpers uses machine, this makes stub making trickier.
AFAIK, the alternative to stub sails helpers is by stubbing the real fn function, because machine will call helper's fn function.
Update: change example that use supertest.
For example:
I create endpoint GET /hello using HelloController,
I use helpers format-welcome-message from helper's example,
I create test spec for endpoint GET /hello.
I run it using mocha without lifecycle.js but embed the lifecycle inside test spec (reference).
Endpoint GET /hello definition:
// File: HelloController.js
module.exports = {
hello: async function (req, res) {
// Dummy usage of helper with predefined input test.
const output = await sails.helpers.formatWelcomeMessage.with({ name: 'test' });
// Just send the output.
res.send(output);
}
};
And do not forget to add route: 'GET /hello': 'HelloController.hello' at config/routes.js.
Test spec contains 3 cases (normal call, stub error, and stub success).
// File: hello.test.js
const sails = require('sails');
const sinon = require('sinon');
const { expect } = require('chai');
const supertest = require('supertest');
describe('Test', function () {
let fwm;
// Copy from example testing lifecycle.
before(function(done) {
sails.lift({
hooks: { grunt: false },
log: { level: 'warn' },
}, function(err) {
if (err) { return done(err); }
// Require helper format welcome message here!
fwm = require('../api/helpers/format-welcome-message');
return done();
});
});
after(function(done) {
sails.lower(done);
});
it('normal case', function (done) {
// Create spy to make sure that real helper fn get called.
const spy = sinon.spy(fwm, 'fn');
supertest(sails.hooks.http.app)
.get('/hello')
.expect(200)
// Expect endpoint output default value.
.expect('Hello, test!')
.end(function() {
// Make sure spy is called.
expect(spy.calledOnce).to.equal(true);
// Restore spy.
spy.restore();
done();
});
});
it('case stub error', function (done) {
// Stub the real fn function inside custom helper.
const stubError = sinon.stub(fwm, 'fn');
stubError.callsFake(async function (input, exits) {
// Setup your error here.
exits.error(new Error('XXX'));
});
supertest(sails.hooks.http.app)
.get('/hello')
.expect(500)
.end(function() {
// Make sure stub get called once.
expect(stubError.calledOnce).to.equal(true);
// Restore stub.
stubError.restore();
done();
});
});
it('case stub success', function (done) {
// Define fake result.
const fakeResult = 'test';
// Stub the real fn function inside custom helper.
const stubSuccess = sinon.stub(fwm, 'fn');
stubSuccess.callsFake(async function (input, exits) {
// Setup your success result here.
exits.success(fakeResult);
});
supertest(sails.hooks.http.app)
.get('/hello')
// Expect endpoint to output fake result.
.expect(fakeResult)
.end(function() {
// Make sure stub get called once.
expect(stubSuccess.calledOnce).to.equal(true);
// Restore stub.
stubSuccess.restore();
done();
});
});
});
When I run it using mocha:
$ npx mocha test/hello.test.js
Test
✓ normal case
error: Sending 500 ("Server Error") response:
Error: XXX
at Object.<anonymous> ...
✓ case stub error
✓ case stub success
3 passing (407ms)
$
How to make my test wait for the result of my api?
I'm using vue and jest to test my components.
I want to test the method that writes a client to my database. In my component I have the following method:
methods: {
onSubmitClient(){
axios.post(`urlApi`, this.dados).then(res => {
return res;
})
}
}
in my test
describe('login.vue', () => {
let wrapper
beforeAll(()=>{
wrapper = mount(client, {
stubs: ['router-link'],
store,
data() {
return {
dados: {
name: 'tes name',
city: 'test city'
},
};
}
})
})
it('store client', () => {
res = wrapper.vm.onSubmitLogin()
console.log(res);
})
})
My test does not wait for the API call to complete. I need to wait for the API call to know if the test worked. How can I make my test wait for API return?
There are several issues in your code.
First, you cannot return from an async call. Instead, you should be probably setting up some data in your onSubmitClient, and returning the whole axioscall, which is a Promise. for instance:
onSubmitClient(){
return axios.post(`urlApi`, this.dados).then(res => {
this.result = res;
return res;
})
}
I assume the method here is storing a result from the server. Maybe you don't want that; it is just an example. I'll come back to it later.
Ok, so now, you could call onSubmitClient in your wrapper and see if this.result is already set. As you already know, this does not work straightforward.
In order for a jest test to wait for asynchronous code, you need either to provide a done callback function or return a promise. I'll show an example with the former:
it('store client', (done) => {
wrapper.vm.onSubmitLogin().then((res) => {
expect(wrapper.vm.dados).toEqual(res);
done();
})
});
Now this code should just work, but still there is an issue with it, as #jonsharpe says in a comment.
You usually don't want to perform real network requests in unitary tests because they are slow and unrealiable. Also, unitary tests are meant to test components in isolation, and here we are testing not only that our component sets this.result properly when the request is made. We are also testing that there is a webserver up and running that is actually working.
So, what I would do in this scenario to test that single piece of functionality, is to extract the request to another method, mock it with vue-test-utils and jest.fn, and then assert that onSubmitClient does its work:
The component:
export default {
data() {
return {
http: axios,
...
},
methods: {
onSubmitClient(){
this.http.post(`urlApi`, this.dados).then(res => {
this.result = res;
})
}
}
}
}
The test:
it('store client', (done) => {
const fakeResponse = {foo: 'bar'};
var post = jest.fn();
var http : {
post,
};
var wrapper = mount(client, {
stubs: ['router-link'],
store,
data() {
return {
dados: {
name: 'tes name',
city: 'test city'
},
http, //now, the component under test will user a mock to perform the http post request.
}
}
});
wrapper.vm.onSubmitLogin().then( () => {
expect(post).toHaveBeenCalled();
expect(wrapper.vm.result).toEqual(fakeResponse);
done();
})
});
Now, your test asserts two things:
post gets called.
this.result is set as it should be.
If you don't want to store anything in your component from the server, just drop the second assertion and the this.result = res line in the method.
So basically this covers why your test is not waiting for the async request and some issues in your code. There are still some things to consider (f.i. I think a global wrapper is bad idea, and I would always prefer shallowMount over mount when testing components behavior), but this answer should help you a lot.
PS: didn't test the code, so maybe I messed up something. If the thing just doesn't work, look for syntax errors or similar issues.
I have a middleware that waits for a ARTICLE_REQUEST action, performs a fetch and dispatches either an ARTICLE_SUCCESS or an ARTICLE_FAILURE action when fetch is done. Like so
import { articleApiUrl, articleApiKey } from '../../environment.json';
import { ARTICLE_REQUEST, ARTICLE_SUCCESS, ARTICLE_FAILURE } from '../actions/article';
export default store => next => action => {
// Prepare variables for fetch()
const articleApiListUrl = `${articleApiUrl}list`;
const headers = new Headers({ 'Content-Type': 'application/json', 'x-api-key': articleApiKey });
const body = JSON.stringify({ ids: [action.articleId] });
const method = 'POST';
// Quit when action is not to be handled by this middleware
if (action.type !== ARTICLE_REQUEST) {
return next(action)
}
// Pass on current action
next(action);
// Call fetch, dispatch followup actions and return Promise
return fetch(articleApiListUrl, { headers, method, body })
.then(response => response.json());
.then(response => {
if (response.error) {
next({ type: ARTICLE_FAILURE, error: response.error });
} else {
next({ type: ARTICLE_SUCCESS, article: response.articles[0] });
}
});
}
I really wonder how to test this async code. I want to see if the follow-up actions will be dispatched properly and maybe if the fetch call gets invoked with the proper URL and params. Can anyone help me out?
PS: I am using thunk although I am not absolutely sure of its function as I just followed another code example
You can mock the fetch() function like so:
window.fetch = function () {
return Promise.resolve({
json: function () {
return Prommise.resolve({ … your mock data object here … })
}
})
}
Or you wrap the entire middleware in a Function like so:
function middlewareCreator (fetch) {
return store => next => action => { … }
}
and then create the middleware with the actual fetch method as parameter, so you can exchange it for tests or production.
I am trying to test javascript method as below,
var spyPostRender = sinon.spy(proxy, "postRender");
var done = assert.async();
proxy.init();
done();
assert.ok(spyPostRender.calledOnce, "postRender() function was called.");
where init() internally calls an ajax service, however when I do this, I am getting below error. Can anybody help me in resolving this issue?
Assertion after the final assert.async was resolved# 85 ms Source:at
Object.QUnit.assert.Assert.ok
(http://code.jquery.com/qunit/qunit-1.17.1.js:1296:8)
FYI - I am using QUnit-1.17.1
Thanks in advance
You are immediately calling the done() function after you call init(), that is incorrect. You should only call the done() method was asynchronous activity has completed (hence the word "done"). The easy way to accomplish this is to add a callback function to your init() method:
proxy.init = function(callback) {
// just using jQuery as an example, could be any framework...
$.ajax({
url: "/some/api/service",
// ...
complete: function() {
callback(/* maybe pass some data back? */);
}
});
};
And then you can pass an anonymous function in when you test it:
QUnit.test("Test the init method", function(assert) {
var spyPostRender = sinon.spy(proxy, "postRender");
var done = assert.async();
proxy.init(function() {
assert.ok(spyPostRender.calledOnce, "postRender() function was called.");
// Notice that we only call done() once everything async is complete!
done();
});
});
First solution is posted by #jakarella, alternative solution without modifying the existing code is using sinon.stub
Or else perhaps use a sinon.stub to override proxy.postRender to perform your async assertions:
var done = assert.async();
var _postRender = proxy.postRender;
var stubPostRender = sinon.stub(proxy, "postRender", function() {
var result = _postRender.apply(this, arguments);
// Do your real assertions BEFORE invoking the final (i.e. you may have multiple) `done` callback
assert.ok(stubPostRender.calledOnce, "postRender() function was called.");
// The final `done` callback MUST be called AFTER all real assertions have been made
done();
return result;
});
proxy.init();
Above code is copied from github solution
https://github.com/jquery/qunit/issues/777
I am trying to spy on $timeout so that I can verify that it has not been called. Specifically, my production code (see below) calls $timeout as a function, not an object:
$timeout(function() { ... })
and not
$timeout.cancel() // for instance
Jasmine, however, requires an object to be spied upon, like this:
spyOn(someObject, '$timeout')
I don't know what 'someObject' would be though.
I am using Angular mocks, if that makes any difference.
Edit: The relevant production code I'm trying to test looks like this:
EventHandler.prototype._updateDurationInOneSecondOn = function (call) {
var _this = this;
var _updateDurationPromise = this._$timeout(function () {
call.duration = new Date().getTime() - call.startTime;
_this._updateDurationInOneSecondOn(call);
}, 1000);
// ... more irrelevant code
}
In the specific test scenario I am trying to assert that $timeout was never called.
Edit 2: Specified clearly that I am using $timeout as a function, not an object.
Ran into the same problem and ended up decorating the $timeout service with a spy.
beforeEach(module(function($provide) {
$provide.decorator('$timeout', function($delegate) {
return sinon.spy($delegate);
});
}));
Wrote more about why this works here.
In angular $timeout is a service that executes/calls a function. The request to "spy" $timeout is a bit odd being the case that what it is doing is executing X function in Y given time. What I would do to spy this services is to "mock" the timeout function and inject it in your controller something like:
it('shouldvalidate time',inject(function($window, $timeout){
function timeout(fn, delay, invokeApply) {
console.log('spy timeout invocation here');
$window.setTimeout(fn,delay);
}
//instead of injecting $timeout in the controller you can inject the mock version timeout
createController(timeout);
// inside your controller|service|directive everything stays the same
/* $timeout(function(){
console.log('hello world');
x = true;
},100); */
var x = false; //some variable or action to wait for
waitsFor(function(){
return x;
},"timeout",200);
...
This code works for me
var element, scope, rootScope, mock = {
timeout : function(callback, lapse){
setTimeout(callback, lapse);
}
};
beforeEach(module(function($provide) {
$provide.decorator('$timeout', function($delegate) {
return function(callback, lapse){
mock.timeout(callback, lapse);
return $delegate.apply(this, arguments);
};
});
}));
describe("when showing alert message", function(){
it("should be able to show message", function(){
rootScope.modalHtml = undefined;
spyOn(mock, 'timeout').and.callFake(function(callback){
callback();
});
rootScope.showMessage('SAMPLE');
expect(rootScope.modalHtml).toBe('SAMPLE');
});
});