Jasmine test case are failing, when testing setTimeout inside function [duplicate] - javascript

This question already has an answer here:
How to test code setup with babel configuration(For Library) using Jasmine?
(1 answer)
Closed 1 year ago.
Good Day Everyone,
I am quite new to testing frameworks Jasmine. We have a TS project setup and I am trying to test a function consisting of setTimeout, but it keeps failing. I am trying to use Clock
One Important point I noticed is that I am using babel-loader as soon as I change the webpack configuration to ts-loader. The test case doesn't fail. (Don't know Why 🤷‍♀️). I have checked multiple times but no luck.
UPDATE
I figured out why the test cases are failing but I have no idea why is it happening.
The configurations of babel and ts-loader are correct I just changed the setTimeout to window.setTimeout(in babel-loader) repository and now the test cases are executing successfully🤷‍♀️. This was just a wild guess from Stack Overflow Link.
I added a console statement of setTimeout and window.setTimeout and found that the definitions of both functions are very different. window.setTimeout has a definition(Green circle in the screenshot) that is the same as after we install Jasmine.clock install. ie. HERE
But setTimeout definition(Red circle in the screenshot) is very different(below).
function (/* ...args */) {
return fn.apply(that, arguments);
}
I tried doing the same in ts-loader repository but here setTimeout and window.setTimeout definitions are the same.
Code:
hideToast() {
setTimeout(() => {
this.showToast = false;
}, 5000);
}
Test Spec:
beforeEach(() => {
// Sometimes calling install() will fail for some reason,
// but calling uninstall() first will make it work
jasmine.clock().uninstall();
jasmine.clock().install();
});
afterEach(() => {
jasmine.clock().uninstall();
});
it('should hide toast', () => {
const obj: Car = new Car();
obj.showToast = true; // This value should change after timeout
obj.hideToast(); // Call the component method that turns the showToast value as false
jasmine.clock().tick(5000);
expect(obj.showToast).toBeFalsy(); // Then executes this
});
Any suggestion would be helpful.
With babel-loader(Test case fail's) Screenshot(Branch = "main"):
https://github.com/dollysingh3192/ts-babel-template
With ts-loader(Test cases passed) Screenshot(Branch = "tsloader"):
https://github.com/dollysingh3192/ts-babel-template/tree/tsloader

Example with clock() using a promise that we resolve straight away:
import { Car } from './car';
describe('Car', () => {
beforeEach(() => {
jasmine.clock().uninstall(); // you probably don't need this line
jasmine.clock().install();
});
afterEach(() => {
jasmine.clock().uninstall();
});
it('should hide toast', () => {
const obj: Car = new Car();
obj.showToast = true; // also here, it's redundant, as set to true in constructor
obj.hideToast();
Promise.resolve().then(() => {
jasmine.clock().tick(5000);
expect(obj.showToast).toBeFalsy();
});
});
});
Other ideas on how to do it in this github issue
But let's forget about the simple (and best solution) for a moment and dig a big into the issue. Without the Clock class, we would have to use the done() callback.
In that case, we would have two solutions:
1 - change jasmine.DEFAULT_TIMEOUT_INTERVAL, to be at least 1ms higher than your timeout:
import { Car } from './car';
describe('Car', () => {
beforeEach(() => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 5001;
});
it('should hide toast', (done) => {
const obj: Car = new Car();
obj.hideToast();
setTimeout(() => {
expect(obj.showToast).toBeFalsy();
done();
}, 5000)
});
});
This works with the done() callback (that might be used while working with async tasks)
and by overriding the jasmine.DEFAULT_TIMEOUT_INTERVAL.
2 - Lower your timeout - by 1 millisecond (yes, 1ms is enough):
import {Car} from './car';
describe('Car', () => {
it('go() works', () => {
const car: Car = new Car();
const returnValue = car.go('vroom');
expect(returnValue).toEqual('vroom');
});
it('should hide toast', (done) => {
const obj: Car = new Car();
obj.hideToast();
setTimeout(() => {
expect(obj.showToast).toEqual(false);
done();
}, 4999);
});
});
Same as before, done callback, but this time we finish 1 millisecond before the Jasmine default timeout and the test will pass (obviously, we need to do it in the class and in the test).
According to the Jasmine docs:

Related

Issue with setting up jest mock

I have the following function that is used within cypress tests for which I want to do unit testing (filterTests.js):
const filterTests = (definedTags, runTest) => {
console.log(`Cypress tags: ${definedTags}`);
let isFound = true;
const includeTag = Cypress.env('INCLUDETAG');
const excludeTag = Cypress.env('EXCLUDETAG');
if (includeTag) {
isFound = definedTags.includes(includeTag);
}
if (excludeTag) {
isFound = ! definedTags.includes(excludeTag);
}
if (isFound) {
runTest();
}
};
export default filterTests;
A test double for Cypress.env needs to be created. I'm not sure if this would technically be considered a stub, mock, fake, dummy, etc..., but the philosophical discussion isn't the focus right now. It looks like in the Cypress world, everything is lumped together under 'mock'.
I started down the path of something like this in the Jest test file:
import filterTests from '../../cypress/support/filterTests';
describe('Something', () => {
jest.mock('Cypress', () => ({
env: {
INCLUDETAG: 'jenkins1'
}
}));
it('Something else ', (done) => {
const tempFunc = () => {
console.log('here a');
done();
};
filterTests(tag, tempFunc);
});
});
But for that I get the error message:
Cannot find module 'Cypress' from 'spec/cypress/filterTestsSO2.test.js'
2 |
3 | describe('Something', () => {
> 4 | jest.mock('Cypress', () => ({
| ^
5 | env: {
6 | INCLUDETAG: 'jenkins1'
7 | }
I believe what is complicating this situation is that Cypress is not explicitly imported in filterTests.js
I think you might just want to set the env value at the top of the test
describe('Something', () => {
Cypress.env(INCLUDETAG, 'jenkins1')
it('Something else ', (done) => {
const tempFunc = () => {
console.log('here a');
done();
};
filterTests(tag, tempFunc); // this function will read the env set above
})
})
Further info - Cypress has a cy.spy() which wraps a method and records it's calls but otherwise leaves it's result the same.
Also cy.stub() which records calls but also provides a fake result.
Jest globals
If you are running the test in Jest, then the Cypress global should be able to be mocked simply by setting it up
global.Cypress = {
env: () => 'jenkins1' // or more complicated fn as test requires
}
Note I expect this will only work with simple cases. Cypress wraps jQuery, Chai and Mocha so they behave slightly differently when a Cypress test runs. If the function you test uses any of those features, even implicitly (like command retry), then Jest will not reproduce the right environment.
My recommendation, test Cypress with Cypress.

Spying on a non-exported node.js function using jest not working as expected

I am trying to mock a non-exported function via 'jest' and 're-wire'.
Here I am trying to mock 'iAmBatman' (no-pun-intended) but it is not exported.
So I use rewire, which does it job well.
But jest.mock doesn't work as expected.
Am I missing something here or Is there an easy way to achieve the same ?
The error message given by jest is :
Cannot spy the property because it is not a function; undefined given instead
service.js
function iAmBatman() {
return "Its not who I am underneath";
}
function makeACall() {
service.someServiceCall(req => {
iAmBatman();
});
return "response";
}
module.export = {
makeACall : makeACall;
}
jest.js
const services = require('./service');
const rewire = require('rewire');
const app = rewire('./service');
const generateDeepVoice = app.__get__('iAmBatman');
const mockDeepVoice = jest.spyOn(services, generateDeepVoice);
mockDeepVoice.mockImplementation(_ => {
return "But what I do that defines me";
});
describe(`....', () => {
test('....', done => {
services.makeACall(response, () => {
});
});
})
It is not entirely clear what your goal is, but if you look at the documentation of jest.spyOn, you see that it takes a methodName as the second argument, not the method itself:
jest.spyOn(object, methodName)
This explains your error: you didn't give the function name, but the function itself.
In this case, using jest.spyOn(services, 'iAmBatman') wouldn't work, since iAmBatman is not exported, and therefore services.iAmBatman is not defined.
Luckily, you don't need spyOn, as you can simply make a new mock function, and then inject that with rewire's __set__ as follows:
(note that I deleted the undefined service.someServiceCall in your first file, and fixed some typos and redundant imports)
// service.js
function iAmBatman() {
return "Its not who I am underneath";
}
function makeACall() {
return iAmBatman();
}
module.exports = {
makeACall: makeACall
}
// service.test.js
const rewire = require('rewire');
const service = rewire('./service.js');
const mockDeepVoice = jest.fn(() => "But what I do that defines me")
service.__set__('iAmBatman', mockDeepVoice)
describe('service.js', () => {
test('makeACall should call iAmBatman', () => {
service.makeACall();
expect(mockDeepVoice).toHaveBeenCalled();
});
})
Another option would be to restructure your code with iAmBatman in a seperate module, and then mock the module import with Jest. See documentation of jest.mock.

How to test if a wrapper in Jest calls skip and it correctly?

This is a follow up question to this question.
I have a function called assertTruthy for Jest. assertTruthy(msg, fn, args), expects a message, a function and arguments and should pass if the thing that is returned when the function is invoked with the arguments is truthy and fail if its not.
I want to extend it to also support Jest's only and skip.
Here is what I wrote:
assertTruthy.skip = ({
message = '',
fn = undefined,
args,
} = {}) => {
it.skip(message, () => {
expect(fn(args)).toBeTruthy();
});
};
assertTruthy.only = ({
message = '',
fn = undefined,
args,
} = {}) => {
it.only(message, () => {
expect(fn(args)).toBeTruthy();
});
};
How would I test these functions?
Here is what I tried, which works, but I'm not sure if this is correct.
describe('skip()', () => {
test('it skips the function', () => {
it.skip = jest.fn();
assertTruthy.skip({
message: 'something',
fn: () => true,
args: undefined,
});
expect(it.skip).toHaveBeenCalledTimes(1);
});
});
This looks like a fair enough test that your assertTruthy skip and only call Jest's it skip and only methods.
You might want to assert that it also calls them with the arguments you expect using toHaveBeenCalledWith.
Can you please elaborate more, what you want to achieve and I did't get it properly why you want to extend jest skip and only methods to achieve the same thing that you are already testing.
But if you only want to test if a function not been invoked/executed with toHaveBeenCalledTimes() based on the arguments to be truthy/falsy then you are doing it right.

How to test `call(fn, params)` with redux-saga

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.

Mocking globals in Jest

Is there any way in Jest to mock global objects, such as navigator, or Image*? I've pretty much given up on this, and left it up to a series of mockable utility methods. For example:
// Utils.js
export isOnline() {
return navigator.onLine;
}
Testing this tiny function is simple, but crufty and not deterministic at all. I can get 75% of the way there, but this is about as far as I can go:
// Utils.test.js
it('knows if it is online', () => {
const { isOnline } = require('path/to/Utils');
expect(() => isOnline()).not.toThrow();
expect(typeof isOnline()).toBe('boolean');
});
On the other hand, if I am okay with this indirection, I can now access navigator via these utilities:
// Foo.js
import { isOnline } from './Utils';
export default class Foo {
doSomethingOnline() {
if (!isOnline()) throw new Error('Not online');
/* More implementation */
}
}
...and deterministically test like this...
// Foo.test.js
it('throws when offline', () => {
const Utils = require('../services/Utils');
Utils.isOnline = jest.fn(() => isOnline);
const Foo = require('../path/to/Foo').default;
let foo = new Foo();
// User is offline -- should fail
let isOnline = false;
expect(() => foo.doSomethingOnline()).toThrow();
// User is online -- should be okay
isOnline = true;
expect(() => foo.doSomethingOnline()).not.toThrow();
});
Out of all the testing frameworks I've used, Jest feels like the most complete solution, but any time I write awkward code just to make it testable, I feel like my testing tools are letting me down.
Is this the only solution or do I need to add Rewire?
*Don't smirk. Image is fantastic for pinging a remote network resource.
As every test suite run its own environment, you can mock globals by just overwriting them. All global variables can be accessed by the global namespace:
global.navigator = {
onLine: true
}
The overwrite has only effects in your current test and will not effect others. This also a good way to handle Math.random or Date.now.
Note, that through some changes in jsdom it could be possible that you have to mock globals like this:
Object.defineProperty(globalObject, key, { value, writable: true });
The correct way of doing this is to use spyOn. The other answers here, even though they work, don't consider cleanup and pollute the global scope.
// beforeAll
jest
.spyOn(window, 'navigator', 'get')
.mockImplementation(() => { ... })
// afterAll
jest.restoreAllMocks();
Jest may have changed since the accepted answer was written, but Jest does not appear to reset your global after testing. Please see the testcases attached.
https://repl.it/repls/DecentPlushDeals
As far as I know, the only way around this is with an afterEach() or afterAll() to clean up your assignments to global.
let originalGlobal = global;
afterEach(() => {
delete global.x;
})
describe('Scope 1', () => {
it('should assign globals locally', () => {
global.x = "tomato";
expect(global.x).toBeTruthy()
});
});
describe('Scope 2', () => {
it('should not remember globals in subsequent test cases', () => {
expect(global.x).toBeFalsy();
})
});
If someone needs to mock a global with static properties then my example should help:
beforeAll(() => {
global.EventSource = jest.fn(() => ({
readyState: 0,
close: jest.fn()
}))
global.EventSource.CONNECTING = 0
global.EventSource.OPEN = 1
global.EventSource.CLOSED = 2
})
If you are using react-testing-library and you use the cleanup method provided by the library, it will remove all global declarations made in that file once all tests in the file have run. This will then not carry over to any other tests run.
Example:
import { cleanup } from 'react-testing-library'
afterEach(cleanup)
global.getSelection = () => {
}
describe('test', () => {
expect(true).toBeTruthy()
})
If you need to assign and reassign the value of a property on window.navigator then you'll need to:
Declare a non-constant variable
Return it from the global/window object
Change the value of that original variable (by reference)
This will prevent errors when trying to reassign the value on window.navigator because these are mostly read-only.
let mockUserAgent = "";
beforeAll(() => {
Object.defineProperty(global.navigator, "userAgent", {
get() {
return mockUserAgent;
},
});
});
it("returns the newly set attribute", () => {
mockUserAgent = "secret-agent";
expect(window.navigator.userAgent).toEqual("secret-agent");
});

Categories