How do use Jest to mock timer context.getRemaining() and mock sendMessage() when it has been called?
I want context.getRemaining() to be decreased by 9sec when sendMessage()has been called. sendMessage() will be called multiple time via while loop. It will start with 60,000ms.
exports.handler = async (context) => {
while (true) {
console.log(context.getRemaining());
await sendMessage();
if (context.getRemaining() < 13000) {
break;
}
}
return "FINISH";
}
You can use jest.fn() for context.getRemaining and provide multiple functions for it:
sendMessage = jest.fn(async () => {});
await handler({
getRemaining: jest.fn()
.mockReturnValueOnce(20000)
.mockReturnValueOnce(10000),
});
expect(sendMessage).toHaveBeenCalledTimes(2)
When you write tests, usually it's better to avoid complex logic in the test code. For the mocks you can provide explicit return values. Else you'll need to write tests for your test code, which is bad.
Related
I have the following scenario:
file1.js:
async function fctionA(event) {
console.log('In fctionA()!');
let responseA = null;
const formattedEvent = formatEvent(event);
...
const b = await fctionB(formattedEvent);
responseA = someLogicUsing(b);
return responseA; // responseA will depend on 'b'
}
file2.js:
async function fctionB(formattedEvent) {
console.log('Now in fctionB()!');
let resB = null;
...
const c = await fctionC(formattedEvent);
...
resB = someLogicDependingOn(c); // resB will depend on 'c'
return resB;
}
async function fctionC(formattedEvent) {
console.log('AND now in fctionC()!');
let c = await someHttpRequest(formattedEvent);
...
return c;
}
Side notes:
Don't mind formatEvent(), someLogicUsing() orsomeLogicDependingOn() too much. Assume it's just sync logic using provided data)
formattedEvent would be anything depending on the original input event. Just to note functions will use it.
PROBLEM:
What i want to do is to unit test fctionA(), using Jasmine: I want to check the responseA after appying the logic on fctionB(), but mock the result of fctionC().
My (clearly) naive approach was:
file1.spec.js
import * as Handler from '../src/file1';
import * as utils from '..src//file2';
describe('fctionA', () => {
let response = null;
beforeAll(async () => {
const mockedEventA = { mockedInput: 'a' };
const mockedC = { mockedData: 1 };
const expectedResponse = { mockedResponse: 1234 };
spyOn(utils, 'fctionB').and.callThrough());
spyOn(utils, 'fctionC').and.returnValue(Promise.resolve(mockedC));
response = await Handler.fctionA(mockedEventA);
});
it('should return a proper response', () = {
expect(response).toEqual(expectedResponse);
});
});
But after checking logs, i can see that ´fctionC()´ does get executed (when as far as i understood, it shouldn't), therefore does not return the mocked result.
Then, after some try and error, i invoked fctionC() directly in fctionA() (instead of indirectly invoking it through´fctionB()´) just to see what happens, and I can spy it and return a mocked value. fctionC() does not execute (can't see log).
So, that makes me think that, at least the way I'm trying to spy on functions, only work for functions that are directly invoked by the function I'm calling, but not for nested ones-
I'm clearly not an expert in Jasmine, so I can't figure out another option. I looked a lot into docs and posts and blogs but nothing worked for me.
Is there a way to achieve what I try here? I guess I might be doing something really silly or not thinking it through.
Thanks!
I have a situation where I need to create a test that calls a function and checks its return value. It returns an object so I have to iterate through it and check 100 or so values for correctness. If one of them fails I want to know which one.
I cannot work out how to do this with vanilla Jest such that the test is self-contained and I get a meaningful error message on a failure.
For example, I can do this: (pseudocode to illustrate, not actual code)
describe('Test for function A', () => {
beforeAll('Create class instance', () => {
this.inst = new MyClass();
});
test('Call function with no parameters', () => {
const value = this.inst.run();
for (each key=value) {
expect(value).toBe(correct); // on failure, doesn't tell me the key, only the value
}
});
});
The problem with this is that if value is not correct then the error message is not very helpful, as it doesn't tell me which of the 100 values has the problem.
I can't change to test.each() because then I get an error saying I have nested test() calls which is not allowed.
If I use an inner test() and change the parent test() to describe() then the code becomes this:
describe('Test for function A', () => {
beforeAll('Create class instance', () => {
this.inst = new MyClass();
});
describe('Call function with no parameters', () => {
const value = this.inst.run();
for (each value) {
test(`Checking ${value.name}`, () => {
expect(value).toBe(correct);
});
}
});
});
This would give me a detailed error message except this.inst.run() is called during test set up, before this.inst has been set by beforeAll(), so it fails. (Jest runs all the describe() blocks first, then beforeAll(), then test(). This means I call this.inst.run() first in a describe() block before the beforeAll() block creates the class instance.)
Is there any way that this is possible to achieve? To have a test that requires an object created and shared amongst all the child tests, a test group that calls a function to get data for that group, then a bunch of tests within the group?
Yes, it is possible according to the order of execution of describe and test blocks:
describe("Test for function A", () => {
this.inst = new MyClass();
afterAll("Create class instance", () => { //--> use this instead of beforeAll
this.inst = new MyClass();
});
test("Should be defined", () => {
//--> at least one test inside describe
expect(inst).toBeTruthy();
});
describe("Call function with no parameters", () => {
const value = this.inst.run();
test("Should be defined", () => {
//--> at least one test inside describe
expect(value).toBeTruthy();
});
for (/*...each value */) {
test(`Checking ${value.name}`, () => {
expect(value).toBe(correct);
});
}
});
});
I came up with a workaround for this. It's a bit hacky but it seems to work. Essentially you use promises to wrap the value you're interested in, so one test will sit there await-ing the result from another test.
Obviously this will only work if the tests are run in parallel, or if the sequential ordering is such that the promise is resolved before it is awaited.
The only trick below is that the await is placed in a beforeAll() block, so that the value is available to all tests within that describe() section. This avoids the need to await in each individual test.
The benefit of this is that the test set up (creating the object) is within a test() so exceptions are captured, and the checks themselves (expect().toBe()) are in separate tests so that the test name can be set to something descriptive. Otherwise if your expect() calls are in a for loop, when one fails there's no way to figure out which array entry was at fault.
It's a lot of work just because you can't supply a description on the expect() call (unlike other testing frameworks), but if you're stuck with Jest then this does at least work. Hopefully one day they will add a per-expect description to avoid all this.
Here is some sample pseudocode:
describe('Test for function A', () => {
let resolveValue;
let promiseValue = new Promise(resolve => resolveValue = resolve);
describe('Create class instance', () => {
test('Run processing', () => {
this.inst = new MyClass();
// inst.run() is now called inside a test(), so any failures will be caught.
const value = this.inst.run();
resolveValue(value); // release 'await promiseValue' below
});
});
describe('Call function with no parameters', () => {
let value; // this is global within this describe() so all tests can see it.
beforeAll(async () => {
// Wait for the above test to run and populate 'value'.
value = await promiseValue;
});
for (each value) {
// Check each value inside test() to get a meaningful test name/error message.
test(`Checking ${value.name}`, () => {
// 'value' is always valid as test() only runs after beforeAll().
expect(value).toBe(correct);
});
}
});
});
UPDATE: Safe to say this is basically a duplicate of Jest: Timer and Promise don't work well. (setTimeout and async function)
I have a function that repeats itself after performing an asynchronous function. I want to verify that jest.advanceTimersOnTime(5000) continues to yield expect(doAsyncStuff).toHaveBeenCalledTimes(X);.
I observe that we can reach the call to repeat the function but it does not "go through" when asking jest to advance timers. It does work when you remove the async function preceding it. So it sounds like I have to get doAsyncStuff to "call through" or resolve some pending promise here?
Function
function repeatMe() {
setTimeout(() => {
doAsyncStuff.then((response) => {
if (response) {
console.log("I get here!");
repeatMe();
}
})
}, 5000);
}
Test
jest.useFakeTimers();
let doAsyncStuff = jest.spyOn(updater, 'doAsyncStuff');
doAsyncStuff.mockResolvedValue(true);
repeatMe();
jest.advanceTimersByTime(5000);
expect(doAsyncStuff).toHaveBeenCalledTimes(1);
jest.advanceTimersByTime(5000);
expect(doAsyncStuff).toHaveBeenCalledTimes(2); // Failed?
So it sounds like I have to...resolve some pending promise here?
Yes, exactly.
The short answer is that a Promise callback gets queued in PromiseJobs by the then chained to the Promise returned by doAsyncStuff, and the way the test is written, that callback never has a chance to run until the test is already over.
To fix it, give the Promise callbacks a chance to run during your test:
updater.js
export const doAsyncStuff = async () => { };
code.js
import { doAsyncStuff } from './updater';
export function repeatMe() {
setTimeout(() => {
doAsyncStuff().then((response) => {
if (response) {
console.log("I get here!");
repeatMe();
}
})
}, 5000);
}
code.test.js
import * as updater from './updater';
import { repeatMe } from './code';
test('repeatMe', async () => {
jest.useFakeTimers();
let doAsyncStuff = jest.spyOn(updater, 'doAsyncStuff');
doAsyncStuff.mockResolvedValue(true);
repeatMe();
jest.advanceTimersByTime(5000);
expect(doAsyncStuff).toHaveBeenCalledTimes(1); // Success!
await Promise.resolve(); // let callbacks in PromiseJobs run
jest.advanceTimersByTime(5000);
expect(doAsyncStuff).toHaveBeenCalledTimes(2); // Success!
await Promise.resolve(); // let callbacks in PromiseJobs run
jest.advanceTimersByTime(5000);
expect(doAsyncStuff).toHaveBeenCalledTimes(3); // Success!
// ... and so on ...
});
The complete details of exactly what happens and why can be found in my answer here
Apparently Jest troubleshooting references this issue: we have set the definition to happen asynchronously on the next tick of the event loop. I guess this applies to events within the live code too, not just the written tests.
Doing jest.runAllTimers() will set things into an endless loop.
Adding jest.runAllTicks() will advance the tick so the above tests now work.
I'm still confused but I guess this is the answer.
jest.advanceTimersByTime(5000);
jest.runAllTicks();
expect(doAsyncStuff).toHaveBeenCalledTimes(1);
jest.advanceTimersByTime(5000);
jest.runAllTicks();
expect(doAsyncStuff).toHaveBeenCalledTimes(2);
https://jestjs.io/docs/en/troubleshooting#defining-tests
Also required for this to work is to mock the implementation of doAsyncStuff because whatever async stuff inside doAsyncStuff also I think gets queued into the tick events.
doAsyncStuff.mockImplementation(() => {
return new Promise.resolve();
});
How do I use async and await in protractor tests?
it('test async', function(){
var value = 0;
function asyncAction() {
return browser.driver.wait(()=>true)
.then(function () {
console.log('a');
return value++;
});
}
//-Problem Area-
async function doAwait(){
await asyncAction();
return asyncAction();
}
doAwait();
protractor.promise.controlFlow().execute( () => {
console.log('b');
expect(value).toBe(2);
});
});
output here is
a
b
a
and value is 1 at time of expect
function doAwait(){
await asyncAction();
return asyncAction();
}
I like to think of this as similar to
function doAwait(){
asyncAction().then(()=>asyncAction());
}
Which works but the above async doAwait does not. I believe this is because the generator breaks the ControlFlow of selenium.
Adding this to the protractor configuration works:
var webdriver = require.main.require('selenium-webdriver');
Promise = webdriver.promise.Promise;
Object.assign(Promise, webdriver.promise);
Promise.resolve = Promise.fulfilled;
Promise.reject = Promise.rejected;
Though maybe not all promises are supposed to be managed promises?
Worth noting that the other solution requires wrapping each async function:
protractor.promise.controlFlow().execute( async () => {
await asyncAction();
return asyncAction();
});
See https://github.com/angular/jasminewd#async-functions--await:
async functions / await
async functions and the await keyword are likely coming in ES2017 (ES8), and available via several compilers. At the moment, they often break the WebDriver control flow. (GitHub issue). You can still use them, but if you do then you will have to use await/Promises for almost all your synchronization. See spec/asyncAwaitAdapterSpec.ts and spec/asyncAwaitErrorSpec.ts for examples.
While unit testing my Node.js app, I encountered a problem with Mocha and ES6 while using setTimeout.
Mocha said the test passed, but when I put in something else (to check the test, to make sure it works), it still says it passed, while it should fail.
Code:
describe('.checkToken', function () {
let user = {};
let token = repository.newToken();
it('token has expired', co.wrap(function* () {
setTimeout(function* () {
let result = yield repository.checkToken(user, token.token);
result.body.should.have.property("error");
}, 1000)
}));
});
});
The other tests are all working and there is no problem in that case.
I've already tried an arrow function or a standard function in the callback of setTimeout, but it then crashes on the yield. (Unexpected token)
checkToken is a generator function.
Using:
Nodejs v4.2.1
Co v4.6.0
Should v7.1.0
Mocha v2.3.3
You cannot use setTimeout with a generator. It's the generator that you pass to co.wrap that will be ran asynchronously, and it needs to know about the timeout. You will need to yield the timeout (as something yieldable, like a thunk or a promise):
it('token has expired', co.wrap(function* () {
yield new Promise(resolve => { setTimeout(resolve, 1000); });
let result = yield repository.checkToken(user, token.token);
result.body.should.have.property("error");
}));