Resetting ajax mock calls count in Jest tests - javascript

I have test structure represented below, my code uses ajax calls so I'm mocking as represented in the Jest tutorial:
describe('it behavior', function() {
it('is as it is', function() {
jQuery.ajax.mock.calls[0][0].success({
data: []
});
...
});
it('is is not as it was', function() {
jQuery.ajax.mock.calls[1][0].success({
data: [1, 2, 3]
});
...
});
});
What bothers me most is this jQuery.ajax.mock.calls[X][0] in every case, mostly because I made typos or forget to increment call number in mock.
Is it possible to reset mock call count in afterEach callback or some pattern for test organisation in that case? Preferably without any extra test dependencies.

Try this:
describe('something', function() {
beforeEach(function() {
jQuery.ajax = jest.genMockFunction()
})
it('does something', function() {
// do something
})
}

Related

Matcher error: received value must be a mock or spy function

I'm writing tests (with Jest and React Testing Library) for a form React component. I have a method that runs on form submit:
const onSubmit = (data) => {
// ...
setIsPopupActive(true);
// ...
};
and useEffect that runs after isPopupActive change, so also on submit:
useEffect(() => {
if (isPopupActive) {
setTimeout(() => {
setIsPopupActive(false);
}, 3000);
}
}, [isPopupActive]);
In the test, I want to check, whether the popup disappears after 3 seconds. So here's my test:
it('Closes popup after 3 seconds', async () => {
const nameInput = screen.getByPlaceholderText('Imię');
const emailInput = screen.getByPlaceholderText('Email');
const messageInput = screen.getByPlaceholderText('Wiadomość');
const submitButton = screen.getByText('Wyślij');
jest.useFakeTimers();
fireEvent.change(nameInput, { target: { value: 'Test name' } });
fireEvent.change(emailInput, { target: { value: 'test#test.com' } });
fireEvent.change(messageInput, { target: { value: 'Test message' } });
fireEvent.click(submitButton);
const popup = await waitFor(() =>
screen.getByText(/Wiadomość została wysłana/)
);
await waitFor(() => {
expect(popup).not.toBeInTheDocument(); // this passes
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 3000);
});
});
However, I'm getting the error:
expect(received).toHaveBeenCalledTimes(expected)
Matcher error: received value must be a mock or spy function
Received has type: function
Received has value: [Function setTimeout]
What am I doing wrong?
Jest 27 has breaking changes for fakeTimers. It seems Jest contributors doesn't update documentation on time. This comment on Github issues confirms it. Moreover, here related PR.
Well, you can solve your problem by two ways.
Configure Jest to use legacy fake timers. In jest.config.js you can add line (but it not works for me):
module.exports = {
// many of lines omited
timers: 'legacy'
};
Configure legacy fake timers for individually test suite, or even test:
jest.useFakeTimers('legacy');
describe('My awesome logic', () => {
// blah blah blah
});
It's preferably to use new syntax based on #sinonjs/fake-timers. But I can't find working example for Jest, so I'll update this answer as soon as possible.
The below approach worked
beforeEach(() => {
jest.spyOn(global, 'setTimeout');
});
afterEach(() => {
global.setTimeout.mockRestore();
});
it('Test if SetTimeout is been called', {
global.setTimeout.mockImplementation((callback) => callback());
expect(global.setTimeout).toBeCalledWith(expect.any(Function), 7500);
})
In your case setTimeout is not a mock or spy, rather, it's a real function. To make it a spy, use const timeoutSpy = jest.spyOn(window, 'setTimeout'). And use timeoutSpy in the assertion.
You could also test not the fact of calling the setTimeout function, but assert that setIsPopupActive was called once, and with false. For this you might need to do jest.runOnlyPendingTimers() or jest.runAllTimers()

In javascript, how do I copy all properties of an object into a block's variable scope?

Is there a way to add to a function or a block's variable scope in javascript in an automated manner? If so, how?
For instance, let's say I had some unit test code like this:
describe('something something', function () {
// tested module
let myTestedModule;
before(function () {
myTestedModule = rewire('../../path/to/my-tested-module');
});
// tested module dependencies
let someRandomLib;
let myDBAdapter;
let util;
let common;
const restoreFunctions = [];
beforeEach(function () {
restoreFunctions.push(myTestedModule.__set__('someRandomLib', new Mock()));
restoreFunctions.push(myTestedModule.__set__('myDBAdapter', new Mock()));
restoreFunctions.push(myTestedModule.__set__('util', new Mock()));
restoreFunctions.push(myTestedModule.__set__('common', new Mock()));
});
afterEach(function () {
lodash.forEach(restoreFunctions, function (restore) {
restore();
});
});
// tests
it('should do some amazing things', function (done) {
// tested call
myTestedModule.doStuff('foo', 'bar');
// assertions
someRandomLib.asdf.should.be.calledWithExactly('foo');
// done
done();
});
});
Most of this actual code would be repeated in multiple tests, and it's trivial to simplify it to something like this:
describe('something something', function () {
const testEnv = new TestEnvironment({
module: 'myTestedModule',
path: '../../path/to/my-tested-module',
dependencies: ['someRandomLib', 'myDBAdapter', 'util', 'common'],
});
it('should do some amazing things', function (done) {
// tested call
testEnv.myTestedModule.doStuff('foo', 'bar');
// assertions
testEnv.someRandomLib.asdf.should.be.calledWithExactly('foo');
// done
done();
});
});
However, the test would be better in terms of readability if I could omit most of the testEnv indexing. It's just plumbing, not part of the actual test.
So, to get the best of both worlds, what I really wanted to write was something like this:
describe('something something', function () {
setupTestEnvironment({
module: 'myTestedModule',
path: '../../path/to/my-tested-module',
dependencies: ['someRandomLib', 'myDBAdapter', 'util', 'common'],
});
it('should do some amazing things', function (done) {
// tested call
myTestedModule.doStuff('foo', 'bar');
// assertions
someRandomLib.asdf.should.be.calledWithExactly('foo');
// done
done();
});
});
To do this, I need a way to import an object's own properties into a variable scope (i.e. this function's variable scope).
In Python you can achieve this with decorators. In Lua you can do this with _M or setfenv. Is it possible at all in Javascript?
I think this would be a good case for destructuring.
it('should do some amazing things', function (done) {
const {myTestedModule, someRandomLib} = testEnv;
// tested call
myTestedModule.doStuff('foo', 'bar');
// assertions
someRandomLib.asdf.should.be.calledWithExactly('foo');
// done
done();
});

Jasmine 2 Spec has no expectations

I have the following test:
describe('when invoked', function() {
it('should check something', function() {
_.each(someData, function(obj, index) {
expect(obj[index].lable).toBe('foo');
});
});
});
When I run Jasmine 2.2.0 it get the following error:
Spec 'SpecLabel function when invoked return value should check something' has no expectations.
Am I missing something? In Jasmin 1.x we could do this. Have expect inside a for each, or even a for loop.
How can I fix these type of tests? And what are the docs for these situations? The Jasmine website is not really helpful.
A quick workaround can be refactoring your tests to:
describe('when invoked', function () {
it('should check something', function () {
var success = true;
_.each(someData, function (obj, index) {
success &= obj[index].lable === 'foo';
});
expect(success).toBeTruthy();
});
});

Node Mocha Chai Async - Everything Passing even when it should fail

I was attempting to teach myself to use a Testing framework for automating tests instead of having to do them by hand. After a bit of trial and error, I finally got the unit tests to start passing ... but now, my problem is everything is passing regardless of if it should or not.
Currently I have the following code:
describe('create {authName, authPW}', function() {
it('no name', function() {
init({
path: ':memory:',
callback: function() {
var arg;
arg = {};
//arg['authName'] = 'Name';
arg['authPW'] = 'Pass';
arg['callback'] = function(r) {
// r.should.equal('create error');
r.should.equal('foobar');
done();
};
create(arg);
}
});
});
});
as you can guess ... r should NOT equal 'foobar'
What am I doing wrong here?
When creating async tests with mocha you need to let him know when it is done
describe('an asynch piece of code', function() {
var foo = new bar();
it('should call the callback with a result', function( done ) {
foo.doAsynchStuff( function( result ) {
result.should.be.ok;
done();
});
});
});
If done is present as an argument on the it then mocha will wait for the done to be called. It has a timeout of 2 seconds, that if exceeded fails the test. You can increase this timeout:
it('should resolve in less than 10 seconds', function( done ) {
this.timeout( 10000 );
foo.doAsynchStuff( function( result ) {
result.should.be.ok;
done();
});
}
it('no name', function(done) {
done has to be an argument of the function passed to it()

Why is Jasmine not resetting a spy when spying on $.ajax?

I am trying to spy on $.ajax in Jasmine 2.0 tests. Here is a simplified example (TypeScript) showing my scenario:
describe("first test", () => {
var deferred = jQuery.Deferred();
spyOn($, "ajax").and.callFake((uri: string, settings: JQueryAjaxSettings) => {
return deferred.resolve("ThisIsADummyResult");
});
it("should return dummy result", done => {
$.ajax("http://somedummyserver.net").then(result => {
expect(result).toBe("ThisIsADummyResult");
done();
});
});
});
describe("second test", () => {
var deferred = jQuery.Deferred();
spyOn($, "ajax").and.callFake((uri: string, settings: JQueryAjaxSettings) => {
return deferred.resolve("ThisIsAnotherResult");
});
it("should return another result", done => {
$.ajax("http://somedummyserver.net").then(result => {
expect(result).toBe("ThisIsAnotherResult");
done();
});
});
});
firstTest as well as second test work if I run them alone. However, if I run both tests as shown above, I get the following error message: ajax has already been spied upon.
So my questions are:
Shouldn't the spies be reset by Jasmine after each test automatically? Why doesn't that work in my case?
Is there another way of using spyOn which makes Jasmine reset the spies?
How can I manually reset the spies?
Update: I continued experimenting and found a possible solution myself. If I set up the spies inside of the it spec, both tests run fine. Here is the code for first test showing what I mean:
describe("first test", () => {
it("should return dummy result", done => {
var deferred = jQuery.Deferred();
spyOn($, "ajax").and.callFake((uri: string, settings: JQueryAjaxSettings) => {
return deferred.resolve("ThisIsADummyResult");
});
$.ajax("http://somedummyserver.net").then(result => {
expect(result).toBe("ThisIsADummyResult");
done();
});
});
});
Still, it would be very interesting why the first version does not work. Why does Jasmine not reset the spies in the first version whereas it does in the second one?
For stuff that is used across all tests but you need it reset for each test use 'beforeEach' : http://jasmine.github.io/2.0/introduction.html#section-Setup_and_Teardown
Jasmine does not magically know which lines of your describe body you want reevaluated for each 'it' block.

Categories