I am learning sinon currently. My codes:
const bluebird = require('bluebird');
const sinon = require('sinon');
const sinonTest = require('sinon-test')(sinon);
sinon.test = sinonTest;
describe('xxx', function _test() {
this.timeout(2000);
it('should', sinon.test(function() {
return new bluebird.Promise( (resolve, reject) => {
try {
console.log('123');
resolve();
} catch ( err ) {
reject(err);
};
})
.then( () => console.log('456') )
.delay(100)
.then( () => console.log('789') )
.then(function() {
})
}));
});
output:
xxx
123
456
Why the above code times out and stuck in delay? Thanks
UPDATE
const bluebird = require('bluebird');
const sinon = require('sinon');
const sinonTest = require('sinon-test')(sinon);
sinon.test = sinonTest;
describe('xxx', function _test() {
this.timeout(2000);
it('should', sinon.test(function() {
return bluebird
.delay(100)
.then( () => console.log('789') );
}));
});
Output:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves
UPDATE
Thanks #Louis. Setting useFakeTimers works fine.
But I am just confused. Why in my project, there are no problems with the existing tests where useFakeTimers set to true by default? If useFakeTimers set true, promise delay cannot be used in sinonTest()?
By the way, I had this problem when upgrading sinon from 1.17.6 to 2.4.1.
Thanks
By default the sandboxes that Sinon creates have their configuration set so that the sandbox configuration option useFakeTimers is true. (Search for defaultConfig in this documentation page.)
This means that while the sandbox is in effect, the clock appears to be stopped, and Bluebird's delay never resolves. You tell sinon-test to create sandboxes without fake timers by passing a 2nd parameter when you configure it. This 2nd parameter is actually a configuration object for Sinon sandboxes:
const sinonTest = require('sinon-test')(sinon,
{ useFakeTimers: false });
I have not tried it, but from eyeballing the code, it looks like you could use multiple configurations at the same time if you need some tests to use fake timers and some to not use fake timers:
const sinonTest = require('sinon-test');
const wrapper = sinonTest(sinon, { useFakeTimers: false });
const wrapperWithTimers = sinonTest(sinon);
You just need to use the right wrapper for the needs of the test.
You added the question:
But I am just confused. Why in my project, there are no problems with the existing tests where useFakeTimers set to true by default? If useFakeTimers set true, promise delay cannot be used in sinonTest()?
By default useFakeTimers is true, but that won't cause a problem unless you have code that depends on the clock moving forward to work properly. I have many test suites where I use sandboxes and where I did not take care to turn off the fake timers and they work fine. Fake timers do not generally prevent asynchronous functions from running. If you do fs.readFile while the sandbox is in effect, for instance, it should work just fine. It just affects functions that depend on the clock, like setTimeout, setInterval and Date.
Bluebird's delay method is affected because it calls setTimeout.
Related
I have the following need to test whether something does not happen.
While testing something like that may be worth a discussion (how long wait is long enough?), I hope there would exist a better way in Jest to integrate with test timeouts. So far, I haven't found one, but let's begin with the test.
test ('User information is not distributed to a project where the user is not a member', async () => {
// Write in 'userInfo' -> should NOT turn up in project 1.
//
await collection("userInfo").doc("xyz").set({ displayName: "blah", photoURL: "https://no-such.png" });
// (firebase-jest-testing 0.0.3-beta.3)
await expect( eventually("projects/1/userInfo/xyz", o => !!o, 800 /*ms*/) ).resolves.toBeUndefined();
// ideally:
//await expect(prom).not.toComplete; // ..but with cancelling such a promise
}, 9999 /*ms*/ );
The eventually returns a Promise and I'd like to check that:
within the test's normal timeout...
such a Promise does not complete (resolve or reject)
Jest provides .resolves and .rejects but nothing that would combine the two.
Can I create the anticipated .not.toComplete using some Jest extension mechanism?
Can I create a "run just before the test would time out" (with ability to make the test pass or fail) trigger?
I think the 2. suggestion might turn handy, and can create a feature request for such, but let's see what comments this gets..
Edit: There's a further complexity in that JS Promises cannot be cancelled from outside (but they can time out, from within).
I eventually solved this with a custom matcher:
/*
* test-fns/matchers/timesOut.js
*
* Usage:
* <<
* expect(prom).timesOut(500);
* <<
*/
import { expect } from '#jest/globals'
expect.extend({
async timesOut(prom, ms) { // (Promise of any, number) => { message: () => string, pass: boolean }
// Wait for either 'prom' to complete, or a timeout.
//
const [resolved,error] = await Promise.race([ prom, timeoutMs(ms) ])
.then(x => [x])
.catch(err => [undefined,err] );
const pass = (resolved === TIMED_OUT);
return pass ? {
message: () => `expected not to time out in ${ms}ms`,
pass: true
} : {
message: () => `expected to time out in ${ms}ms, but ${ error ? `rejected with ${error}`:`resolved with ${resolved}` }`,
pass: false
}
}
})
const timeoutMs = (ms) => new Promise((resolve) => { setTimeout(resolve, ms); })
.then( _ => TIMED_OUT);
const TIMED_OUT = Symbol()
source
The good side is, this can be added to any Jest project.
The down side is, one needs to separately mention the delay (and guarantee Jest's time out does not happen before).
Makes the question's code become:
await expect( eventually("projects/1/userInfo/xyz") ).timesOut(300)
Note for Firebase users:
Jest does not exit to OS level if Firestore JS SDK client listeners are still active. You can prevent it by unsubscribing to them in afterAll - but this means keeping track of which listeners are alive and which not. The firebase-jest-testing library does this for you, under the hood. Also, this will eventually ;) get fixed by Firebase.
If I have the following module:
module.exports = kontinue => {
Promise.resolve({error:null})
.then(o => {
console.log('promise resolved');
// say something goes wrong here
if(true)
kontinue({error:'promise resolved but something else went wrong'});
else kontinue(o);
})
.catch(error => {
console.log('caught error');
kontinue({error:'promise rejected, or resolved but then continuation threw exception'})
});
};
And the following test:
const assert = require('assert').strict;
const target = require('./the_above_code.js');
it('should not timeout', (done) => {
target((sut) => {
console.log('continuation called');
assert.ok(false); // the test for sut.error === what I expected was failing
done();
});
});
It outputs:
promise resolved
continuation called
caught error
...
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
I realise this is because the .catch() is returning a new promise which is not resolving, but that's not what I really want during testing.
How do I test the object a promise resolves to, fail the test if necessary, have Mocha report that failure?
Perhaps there is somewhere else other than in the continuation (which never returns in the code that uses this module) that I can put the tests?
I'm sure monads can reduce the amount of boilerplate code here, but using them surely would violate Kernighan's maxim.
I wish to render a page using Nuxt's renderAndGetWindow in order to test the values returned by my API.
Here's how I do it:
// Nuxt instance
let nuxt = null;
// Our page to test
let homePage = null;
beforeAll(async (done) => {
// Configuration
const rootDir = resolve(__dirname, '../..');
let config = {};
config = require(resolve(rootDir, 'nuxt.config.js'));
config.rootDir = rootDir; // project folder
config.env.isDev = false; // dev build
config.mode = 'universal'; // Isomorphic application
nuxt = new Nuxt(config);
await new Builder(nuxt).build();
nuxt.listen(3001, 'localhost');
homePage = await nuxt.renderAndGetWindow('http://localhost:3001/');
});
Which gives me 2 distinct errors:
console.error node_modules/jest-jasmine2/build/jasmine/Env.js:157
Unhandled error
console.error node_modules/jest-jasmine2/build/jasmine/Env.js:158
TypeError: setInterval(...).unref is not a function
And
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
at mapper (node_modules/jest-jasmine2/build/queue_runner.js:41:52)
I get this ever since I switched from Ava to Jest.
Am I handling my rendering wrong?
unref
The default test environment for Jest is a browser-like environment through jsdom.
unref is a special function provided by Node. It is not implemented in browsers or in jsdom, but it is implemented in the "node" test environment in Jest.
It looks like testing a Nuxt app requires both a Node environment to start a server, and a jsdom environment to test the resulting UI.
This can be done by setting the test environment to "node" and initializing a window using jsdom during the test setup.
timeout
Jest will "wait if you provide an argument to the test function, usually called done". This applies to test functions and setup functions like beforeAll.
Your beforeAll function has an argument done that is never called. Jest will wait until either done is called or the timeout configured with jest.setTimeout expires (defaults to 5 seconds).
You are using an async function and are using await on what looks to be the asynchronous part of the function so you don't need done. Change your beforeAll function to not take any parameters and that will prevent Jest from waiting for done to be called.
In my tests starting the Nuxt server takes quite a while so you can pass a timeout value as an additional parameter to beforeAll to increase the timeout for just that function.
Here is an updated test with these changes:
/**
* #jest-environment node
*/
// TODO: Set the environment to "node" in the Jest config and remove this docblock
// TODO: Move this to a setup file
const { JSDOM } = require('jsdom');
const { window } = new JSDOM(); // initialize window using jsdom
const resolve = require('path').resolve;
const { Nuxt, Builder } = require('nuxt');
// Nuxt instance
let nuxt = null;
// Our page to test
let homePage = null;
beforeAll(async () => {
// Configuration
const rootDir = resolve(__dirname, '../..');
let config = {};
config = require(resolve(rootDir, 'nuxt.config.js'));
config.rootDir = rootDir; // project folder
config.env.isDev = false; // dev build
config.mode = 'universal'; // Isomorphic application
nuxt = new Nuxt(config);
await new Builder(nuxt).build();
nuxt.listen(3001, 'localhost');
homePage = await nuxt.renderAndGetWindow('http://localhost:3001/');
}, 20000); // Give the beforeAll function a large timeout
afterAll(() => {
nuxt.close();
});
describe('homepage', () => {
it('should do something', () => {
});
});
As far as I can tell, using promises or callbacks in After hook prevents Command Queue from executing when using promises / callbacks. I'm trying to figure out why, any help or suggestions are appreciated. Closest issue I could find on github is: https://github.com/nightwatchjs/nightwatch/issues/341
which states: finding that trying to make browser calls in the after hook is too late; it appears that the session is closed before after is run. (exactly my problem). But there is no solution provided. I need to run cleanup steps after my scenarios run, and those cleanup steps need to be able to interact with browser.
https://github.com/nightwatchjs/nightwatch/wiki/Understanding-the-Command-Queue
In the snippet below, bar is never outputted. Just foo.
const { After } = require('cucumber');
const { client } = require('nightwatch-cucumber');
After(() => new Promise((resolve) => {
console.log('foo')
client.perform(() => {
console.log('bar')
});
}));
I also tried using callback approach
After((browser, done) => {
console.log('foo');
client.perform(() => {
console.log('bar');
done();
});
});
But similar to 1st example, bar is never outputted, just foo
You can instead use something like:
const moreWork = async () => {
console.log('bar');
await new Promise((resolve) => {
setTimeout(resolve, 10000);
})
}
After(() => client.perform(async () => {
console.log('foo');
moreWork();
}));
But the asynchronous nature of moreWork means that the client terminates before my work is finished, so this isn't really workin for me. You can't use an await in the perform since they are in different execution contexts.
Basically the only way to get client commands to execute in after hook is my third example, but it prevents me from using async.
The 1st and 2nd examples would be great if the command queue didn't freeze and prevent execution.
edit: I'm finding more issues on github that state the browser is not available in before / after hooks: https://github.com/nightwatchjs/nightwatch/issues/575
What are you supposed to do if you want to clean up using the browser after all features have run?
Try the following
After(async () => {
await client.perform(() => {
...
});
await moreWork();
})
I have a working ractive component test case already with mocha using sinon ans able to mock an ajax call but with the help of setTimeout(function(){}, 100) and I dont like using it.
beforeEach(function () {
this.container = document.createElement('div');
document.body.appendChild(this.container);
this.server = sinon.fakeServer.create();
this.server.respondWith(
"GET",
"/api/url",
[
200,
{ "Content-Type": "application/json" },
'{"data": []}'
]
);
});
afterEach(function () {
document.body.removeChild(this.container);
});
it("should fetch data from server", function (done) {
var server = this.server;
var Component = require('rvc!path/to/component');
var component = new Component({
el: this.container,
});
setTimeout( function() {
server.respond();
expect(component.findAll('.list li').length).to.equal(7);
done();
}, 100);
});
As you can see in the code above, I'm using the setTimeout to make sure that the ajax call (mock) was made before having the actual test of the component.
Is there a way that I can eliminate the setTimeout having the same effect? Thanks!
Assuming that your http request is happening in the component, then it won't get the data until the next tick of the event loop.
One approach is to use setTimeout as you have done, but you probably can lower the timeout (use autoRespondAfter to adjust the sinon response delay).
If your component code supports it, an approach that seems well suited to your use case from the sinon fakeServer docs would be to use the respondImmediately option:
If set, the server will respond to every request immediately and
synchronously. This is ideal for faking the server from within a test
without having to call server.respond() after each request made in
that test. As this is synchronous and immediate, this is not suitable
for simulating actual network latency in tests or mockups.
this.server = sinon.fakeServer.create({ respondImmediately: true })
// now no need to call server.respond() and calls are returned synchronously!