Sinon: Can't install fake timers twice on the same global object - javascript

We have been using useFakeTimers() (sinon v11.x) in many spec files for quite a long time. Recently, we have updated our sinon to 14.x version, now the tests are failing with below error.
TypeError: Can't install fake timers twice on the same global object.
We have tried with createSandbox() also, didn't help.

The issue seems like after Sinon 12.x, not restoring the clock in the spec files, injecting it into global scope which throws the aforementioned error.
So the fix is, call clock.restore() in afterAll() or afterEach() based on whether you used beforeAll() or beforeEach().

So, I encountered this error, for instance, if I had two tests which both used fake timers. You have to call useFakeTimers independently of your sandbox creation.
Fails miserably because reasons
/// Somefile
const superTrialAndErrorSimulator = sinon.createSandbox({
useFakeTimers: true
});
// Some other file
const superTrialAndErrorSimulatorZool = sinon.createSandbox({
useFakeTimers: true
});
If you set fake timers after setting the sandbox, then reset them, it works. Welcome to the trial and error world of sinon.
Works miserably because reasons
const ifOnlyThereWereABetterLibrary = sinon.createSandbox();
before(() => {
ifOnlyThereWereABetterLibrary.useFakeTimers();
});
after(() => {
ifOnlyThereWereABetterLibrary.clock.restore();
});
// Works.

Related

Cypress doesn't take screenshots in CLI mode

I'm trying to take a simple screenshot using Cypress, however, it only works in the Cypress GUI (cypress open).
Whenever I run cypress run, it shows that the test succeeded but there's not screenshot saved.
This my code:
describe('Snapshot', () =>
it.only('Take snapshot', async () => {
cy.visit("http://www.google.com");
cy.screenshot();
});
});
Actually, even in the GUI you may get inconsistent behaviour with the code you've provided: you should try to refactor your tests to avoid the use of async.
Cypress already does a lot of async stuff itself. See this answer for more information about how cypress uses promises, and avoiding async, as well as a few things you could try if you really want to use async.
Your code will successfully create screenshots under cypress run if you remove the async keyword like this:
describe('Snapshot', () => {
it.only('Take snapshot', () => {
cy.visit("http://www.google.com");
cy.screenshot();
});
});
Note: I also added the missing opening brace { at the end of the first line. Since you say this code ran for you in the GUI, I'm assuming the brace was not the issue but was simply lost while posting.

How can jest test a function that uses performance.getEntries()?

When running jest, if a function makes use of performance.getEntries (or performance.getEntriesByName) then the test throws an exception:
performance.getEntries is not a function
How can I allow my tests to continue running without crashing?
According to the documentation, performance.getEntries was removed from perf_hooks in NodeJS v10.0.0 (https://github.com/nodejs/node/pull/19563) and the same functionality may be achieved using
const measures = []
const obs = new PerformanceObserver(list => {
measures.push(...list.getEntries())
obs.disconnect()
})
obs.observe({entryTypes: ['measure']})
function getEntriesByType(name) {
return measures
}
However the above solution is only viable when working directly in the NodeJS application, not when running tests as I do not want to change my implementation. I have tried mocking performance.getEntries as a global in setupAfterEnv, as I would for other globals. For example:
global.performance = {
getEntries: () => []
}
However when debugging, performance is always equal to {} (while performance.now() works well. I believe this is default jsdom (which is my testEnvironment).

How to pass a value from one Jasmine JavaScript test to another test

I am testing reading and writing (to a server, to a mongo db).
I know, I am not supposed to do this, I should be using mocks, ... but anyhow
I want to write a document, read that document to make sure it is was correctly written,
then delete that document, then verify it is gone. So I have 2 problems that I have solved but by using 2 hacks.
1) how do I pass along the mongo _id of the document from step to step. I'd like to have a simple variable in my Jasmine code that I can read and write from each test. I am hacking it now by creating a variable in the actual Angular module that I am testing, and reading and writing a variable over in that code.
2) since I have to wait for each IO operation before proceeding, I am taking advantage of the
setTimeout(() => {done();}, 2000); feature in a set of nested beforeEach(function(done){
sections.
I would like to learn simple, better ways of doing these if there are any.
thanks
What you're doing is called integration tests. Nothing wrong with doing them, but I usually write integration tests using Angular's E2E facilities.
That said, just save the value in a global variable and it will change each test. Some psuedo code
describe('integration test', () => {
let id;
it('should create a document', () => {
// code to create item and return id
id = _id;
}
it('should load document', () => {
console.log(id); // should be value from create test
}
it('should delete document, () => {
console.log(id); // should have value from create test
}
}
Since the id value is never set in a beforeEach() it will retain its value between tests in the same describe() block.
I have cautions about this when writing unit tests--because the tests must run in a specific order to execute. But, the desire is that E2E / integration tests are run sequentially.

How to wait for a function to be called when testing using Selenium WebDriver/Jest?

I have an application that I created using Ext JS and I am writing tests for it using Selenium WebDriver (the Node package - version 4.0.0-alpha.1) and Jest. In one of my test scripts, I want to wait for a function to be called before continuing with the remaining test logic but I am not sure how to implement this. To help demonstrate what I am trying to accomplish, I created a sample app using Sencha Fiddle. All of the code for that app as well as a running version of the app can be found here: https://fiddle.sencha.com/#view/editor&fiddle/2o6m. If you look in the app folder of the fiddle, you'll see that there is a Test component with a simple controller. There is an onAdd function in the controller and that is the function I want to wait for before continuing since in my actual application there is code in that function that the rest of the tests are dependent on. I can access that function in dev tools by running the following line of code: Ext.ComponentQuery.query('test')[0].getController().onAdd (note that the activeElement needs to be set to the preview iFrame (document.getElementsByName('fiddle-run-iframe')[0]) in the fiddle for this to work). This means I can access the function in driver.executeScript the same way, but once I have the function, I am not sure how to wait for it to be called before continuing. I was trying to use the mock/spy feature in Jest, but this is not working because jest is not defined inside driver.executeScript so I can't call jest.fn or jest.spyOn. I created a sample test script that works with the sample app to demonstrate what I am trying to do, but right now it fails with an error since, as I said, jest is not defined inside driver.executeScript. Here is the code for the sample test script:
const {Builder, By, Key, until} = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
const driver = global.driver = new Builder()
.forBrowser('chrome')
.setChromeOptions(new chrome.Options())
.build();
jest.setTimeout(10000);
beforeAll(async () => {
await driver.manage().window().maximize();
await driver.get('https://fiddle.sencha.com/#view/editor&fiddle/2o6m');
});
afterAll(async () => {
await driver.quit();
});
describe('check title', () => {
it('should be SAMPLE STORE LOAD', async () => {
expect(await driver.wait(until.elementLocated(By.css('.fiddle-title'))).getText()).toBe('SAMPLE STORE LOAD');
});
});
describe('check store add', () => {
it('should call add function', async () => {
let spy;
await driver.switchTo().frame(await driver.findElement(By.name('fiddle-run-iframe')));
await driver.wait(until.elementIsNotVisible(await driver.wait(until.elementLocated(By.xpath('//div[starts-with(#id, "loadmask")]')))));
await driver.executeScript(() => {
const test = document.getElementsByName('fiddle-run-iframe')[0].contentWindow.Ext.ComponentQuery.query('test')[0];
spy = jest.spyOn(test.getController(), 'onAdd'); //This throws an error since jest is not defined inside driver.executeScript
});
expect(spy).toHaveBeenCalled(); //wait for onAdd function to be called before continuing
});
//additional tests occur here after wait...
});
You can ignore all the logic related to switching to the iFrame because that is only necessary for the fiddle since it runs the preview of the app in an iFrame. My actual app does not exist inside an iFrame. Nonetheless, I think this script effectively demonstrates what I am trying to accomplish which is to wait until the onAdd function is called before continuing with my tests. I am not sure if I need to use Selenium or Jest, some combination of the two, or a different testing tool entirely to do this. I am relatively new to writing tests and this is my first time posting on Stack Overflow so I apologize if anything I said is unclear. I would be happy to clarify anything if you have any questions and grateful for any advice you have to offer!
From my point of view, combining 2 different frameworks (approaches to testing) in a single test might not be the best idea. Here are some thoughts on how I would deal with a similar case. Very little theory:
Selenium - is great in simulation end-user behavior with a browser. Basically, it can simulate actions (clicks, text inputs, etc.) and can get information from a browser window (presence of elements, texts, styles, etc.)
Jest - is a unit test framework to jest javascript code.
executeScript is a method of Selenium, it executes any javascript code in a browser. Browser does not know anything about jest - so it's expected that you faced an error you described. Your project knows about jest, as it's specifically imported in a project.
Back to the question "How to check in Selenium that a js function has been called?"
The most typical solution - is to wait until something has changed on a browser screen. Typical cases:
some element appears
some element disappears
some element's style changed
wait for condition in js example
(a bit of a hack) Another possible solution is to add a some flag in the js code of an app that will be false before function is called and will be set to true after function is called. Then you can access this flag's value in the executeScript.
some possible implementations here

How to handle global objects / tests influencing other tests in Jest

Some of my tests in my React Native project affect global objects. These changes often affect other tests relying on the same objects.
For example: One test checks that listeners are added correctly, a second test checks if listeners are removed correctly:
// __tests__/ExampleClass.js
describe("ExampleClass", () => {
it("should add listeners", () => {
ExampleClass.addListener(jest.fn());
ExampleClass.addListener(jest.fn());
expect(ExampleClass.listeners.length).toBe(2);
});
it("should remove listeners", () => {
const fn1 = jest.fn();
const fn2 = jest.fn();
ExampleClass.addListener(fn1);
ExampleClass.addListener(fn2);
expect(ExampleClass.listeners.length).toBe(2);
ExampleClass.removeListener(fn1);
expect(ExampleClass.listeners.length).toBe(1);
ExampleClass.removeListener(fn2);
expect(ExampleClass.listeners.length).toBe(0);
});
});
The second test will run fine by itself, but fails when all tests are run, because the first one didn't clean up the ExampleClass. Do I always have to clean up stuff like this manually in each test?
It seems I'm not understanding how the scope works in Jest... I assumed each test would run in a new environment. Is there any documentation about this?
Other examples are mocking external libraries and checking if the mocked functions in them are called correctly or overriding Platform.OS to ios or android to test platform-specific implementations.
As far as I can see, the scope of a test is always the test file. For now I changed my code to have a beforeEach callback that mainly calls jest.resetAllMocks() and resets Platform.OS to its default value.
Fast and sandboxed
Jest parallelizes test runs across workers to maximize performance. Console messages are buffered and printed together with test results. Sandboxed test files and automatic global state resets for every test so no two tests conflict with each other.
https://facebook.github.io/jest/

Categories