Trying to unit test my controller, but when I do so I'm getting the following error.
I'm open to answers with a different way of testing my controller.
Error:
TypeError: expected sinon object
const test = require('sinon-test');
describe('index (get all)', function() {
beforeEach(function() {
res = {
json: sinon.spy(),
status: sinon.stub().returns({ end: sinon.spy() })
};
expectedResult = [{}, {}, {}];
});
it(
'should return array of vehicles or empty array',
test(() => {
this.stub(Vehicle, 'find').yields(null, expectedResult);
Controller.index(req, res);
sinon.assert.calledWith(Vehicle.find, {});
sinon.assert.calledWith(res.json, sinon.match.array);
})
);
});
First of all, when asking a StackOverflow question, it makes sense to post a fully runnable example and stating all dependencies. Basically, I used more than an hour trying to test this out because both were missing.
This is the fully expanded example, just with dummy implementations of your two main objects.
var sinon = require("sinon");
var sinonTest = require("sinon-test");
var test = sinonTest(sinon);
const Vehicle = {
find() {}
};
const Controller = {
index() {}
};
describe("index (get all)", function() {
let expectedResult, res, req;
beforeEach(function() {
res = {
json: sinon.spy(),
status: sinon.stub().returns({ end: sinon.spy() })
};
expectedResult = [{}, {}, {}];
});
it(
"should return array of vehicles or empty array",
test(function() {
this.stub(Vehicle, "find").yields(null, expectedResult);
Controller.index(req, res);
sinon.assert.calledWith(Vehicle.find, {});
sinon.assert.calledWith(res.json, sinon.match.array);
})
);
});
Now, to your question, which was why you were getting an error. The first thing to test is: does the bug appear when I update to the latest versions of the dependencies of the test? The answer is, no, it does not appear. So basically, this is about you using the sinon-test version 2.0, which had a compatibility bug with Sinon 3. This is from the changelog:
2.1.0 / 2017-08-07
==================
Fix compatibility with Sinon 3 (#77)
2.0.0 / 2017-06-22
==================
* Simplify configuration API (#74)
So, given that has been fixed, and the example below is being used, the test is fully runnable:
mocha mytest.js
index (get all)
1) should return array of vehicles or empty array
0 passing (6ms)
1 failing
1) index (get all)
should return array of vehicles or empty array:
AssertError: expected find to be called with arguments
The error here is of course not really an error, but simply a byproduct of me not having the full implementation of your controller and vehicle classes.
Related
I'm quite new with jest testing and I'm having trouble to understand how Jest deals with the functions I'm trying to test. Here's my problem:
I'm trying to test the following, quite simple function, which will receive a bookId and will find the object within an array that containa such id. All is vanilla js, no react.
function catchSelectedBook(bookId) {
const objectSearchAsString = localStorage.getItem('objecttransfer');
const booksObject = JSON.parse(objectSearchAsString);
const currentBook = booksObject.filter((book) => book.id === bookId);
return currentBook;
}
The unit test Jest code is the following:
describe('Given a function that is given as argument the id', () => {
test('When invoked, it finds the book that matches such id', () => {
const returnMokObject = {
kind: 'books#volumes',
totalItems: 1080,
items: [
{
kind: 'books#volume',
id: 'HN1dzQEACAAJ',
}],
};
mockLocalstorageJest();
const answer = catchSelectedBook('HN1dzQEACAAJ');
expect(answer.id).toBe('HN1dzQEACAAJ');
});
});
The function mockLocalstorageJest sends to the local storage an object so that it can be get when the function catchSelectedBook is tested
I export my function like this:
module.exports = {
catchSelectedBook,mockLocalstorageJest,
};
And I import the function into the test file like this:
const { catchSelectedBook, mockLocalstorageJest} = require('./book-details.js');
Whenever I run the test, I got the following error message:
enter image description here
Does that mean that Jest doesn't have the array method "filter" defined?
Thanks!
I think that this instruction is returning null
const objectSearchAsString = localStorage.getItem('objecttransfer');
JSON.parse(null) returns null so bookObject is also null.
I believe that your problem is that you are not setting up the local storage correctly in mockLocalstorageJest().
newbie testing here and I ran into this error
module.js
import reducer from './reducer';
export default function getGModalModule({ domain }) {
return {
id: `GModal-module-${domain}`,
reducerMap: {
[domain]: reducer({
domain,
}),
},
};
}
module.test.js
import getGModalModule from '../module';
import reducer from '../reducer';
describe('getGModalModule', () => {
it('returns the correct module', () => {
const reducers = reducer({
domain: 'demo'
})
const expectVal = getGModalModule({
domain: 'demo'
});
const received = {
id: `GModal-module-demo`,
reducerMap: {
demo: reducers,
},
}
console.log("expectVal", expectVal)
console.log("received", received)
expect(expectVal).toEqual(received);
});
});
The error says something like this:
Expected: {"id": "GModal-module-demo", "reducerMap": {"demo": [Function gModalReducer]}}
Received: serializes to the same string
I've tried to log out the result and they look exactly the same, any idea why this doesn't pass the test ?
expectVal { id: 'GModal-module-demo', reducerMap: { demo: [Function: gModalReducer] } }
received { id: 'GModal-module-demo', reducerMap: { demo: [Function: gModalReducer] } }
Thanks in advance,
Jest cannot compare functions. Even if they are serialized into the same string, even if their source code is the same, function may refer to different variables through closures or bound to different this so we cannot say if functions are equal.
expect(function aaa() {}).toEqual(function aaa() {}); // fails
You may want to exclude methods before comparison explicitly or create custom matcher that will do that for you. But much better if you test what functions does instead. In your case you probably have separate test for ./reducer.js, right? You can just join both test: that one testing reducer in isolation and this one that checks object key names.
I am trying to load files into an array and then run tests on them. This is my code:
let files: string[] = []
describe.only("Name of the group", () => {
beforeAll(() => {
files = ["a", "b"]
})
test.each(files)("runs", f => {
console.log(f)
})
})
However, I get
Error: .each called with an empty Array of table data.
What am I doing wrong?
Thanks!
test.each expects a table value as input. which means an array of arrays. But that is fixed automatically so don't worry about it.
But the call order is important here! Notice that the tests are defined before they are actually run. So, beforeAll will run after the tests were defined. This means the files array won't be defined while the tests are being registered.
In order to fix this, you need to make sure the files array is populated before the tests are read and registered
So something like this:
const files: string[][] = [ ['test1'],['test2'] ];
describe('Something Something', () => {
describe('Name of the group', () => {
test.each(files)('runs %s', (f) => {});
});
});
I'm seeing some strange results when testing what I thought were simple saga's with redux-saga.
Take this saga for example:
export function* initSaga() {
yield call(window.clearTimeout, runner)
yield call(PubSub.publishSync, 'RESET_CORE')
}
My mental model of the test reads, first check it called window.clearTimeout with a parameter which matches the value of runner then test it called the publishSync method of PubSub with the value 'RESET_CORE'
My test for the first part reads:
describe('when testing the init saga', () => {
const saga = initSaga()
const runner = null
it('first clears the draw loop for the canvas', () => {
const result = saga.next().value
const expected = call(window.clearInterval, runner)
console.log(result)
console.log(expected)
expect(result).to.deep.equal(expected)
})
})
What's frustrating is that the error message reads:
AssertionError: expected { Object (##redux-saga/IO, CALL) } to deeply equal { Object (##redux-saga/IO, CALL) }
And my console logs read:
console.log src\tests\sagas\simulatorSaga.test.js:25
{ '##redux-saga/IO': true,
CALL:
{ context: null,
fn: [Function: bound stopTimer],
args: [ null ] } }
console.log src\tests\sagas\simulatorSaga.test.js:26
{ '##redux-saga/IO': true,
CALL:
{ context: null,
fn: [Function: bound stopTimer],
args: [ null ] } }
Which to me look identical. I'm presuming there is something simple I'm missing here, some kind of deep equal object reference kind of stuff but I'm not sure bearing in mind the saga and test how I could make this any more cut down.
I'm aware that the whole window + pubsub isn't exactly how saga's are meant to be used, but the app is interfacing with a canvas for running a simulation so we kind of have to do it that way.
I know that that's what the official docs suggest but I don't like such testing. It looks a little bit like testing if redux-saga does its job. What you are really interested in is if clearInterval and publishSync are executed with correct parameters. You don't care what the generator returns. So here's what I'm suggesting:
const executeSaga = function (saga) {
var result = saga.next();
while(!result.done) {
result = saga.next();
}
}
describe('when testing the init saga', () => {
const runner = null;
it('first clears the draw loop for the canvas', () => {
sinon.stub(window, 'clearInterval');
sinon.stub(PubSub, 'publishSync');
executeSaga(initSaga());
expect(window.clearInterval)
.to.be.calledOnce
.and.to.be.calledWith(runner);
expect(PubSub.publishSync)
.to.be.calledOnce
.and.to.be.calledWith('RESET_CORE');
window.clearInterval.restore();
PubSub.publishSync.restore();
})
})
It involves sinonjs. We are stubbing both functions, run the whole saga and then expect the stubs.
Code example
This is probably better explained in code, so I have included a detailed, abstracted example below - where my question is repeated in the comments.
Example Summary
For example, you mock out a reasonably complex mock service, and in the first test you want the mock service to give a positive result for one function (by return code 200 in the example below). In the second test, you want the same function to return a negative result, say 500.
I found that I can simply inject the provider and overwrite the method, but is that the correct way? See "test 2" below.
describe('CommentArea', function() {
var controller
beforeEach(function() {
module('app', function($provide) {
// Base mock definition
$provide.provider('comments', function() {
this.$get = function() {
return {
create: function() {
return new Promise(function(resolve) {
resolve({ code: 200 })
})
},
delete: function() {
return new Promise(function(resolve) {
resolve({ code: 200 })
})
}
}
}
})
})
inject(function($componentController) {
controller = $componentController('CommentArea')
})
})
// Start tests
//
// Test 1 - Use the above mocked service "as is" - happy path scenario
//
it('appends comment when successful', function() {
expect(controller.commentCount).toEqual(0)
controller.create() // uses the comments service create method
expect(controller.commentCount).toEqual(1)
})
// Test 2 - Use the above mocked service with a slight modification
// This time the create function will fail
//
it('does not append comment when unsuccessful', inject(function(comments) {
// overwrite for failure condition here
// QUESTION - is this acceptable? It seems odd just overwriting it
// so directly
comments.create = function () {
return new Promise(function(resolve) {
resolve({ code: 500 })
})
}
expect(controller.commentCount).toEqual(0)
controller.create() // uses the comments service create method
expect(controller.commentCount).toEqual(0)
}))
})
Usually this means that mocked return values shouldn't be defined in beforeEach.
One of the ways is to make functions return local variables that are defined in common function scope (describe block):
var createValue;
...
beforeEach(module('app', { comments: {
create: function () { return createValue }, ...
}))
...
createValue = Promise.resolve(200);
comments.create();
Local variables have a downside. If createValue wasn't redefined by mistake in next test, tests may become cross-contaminated.
Moreover, this kind of mocks doesn't solve an important task; mocked functions aren't spied.
This is exactly the task that Jasmine spies are supposed to solve:
beforeEach(function () {
module('app', { comments: jasmine.createSpyObj('comments', ['create', 'delete']) });
});
module should be wrapped with a function in this case because a new spy object is supposed to be created on each beforeEach call.
Then return values are defined in-place:
comment.create.and.returnValue(Promise.resolve(200))
comments.create();