I want to create a beforeEach Hook that is executed before a before Hook.
Basically I want the following behaviour:
beforeEach(() => {
console.log('beforeEach')
})
describe('tests', () => {
before(() => {
console.log('before')
})
it('test 1', () => {
console.log('it')
})
})
And I get:
before
beforeEach
it
But the output I want is:
beforeEach
before
it
What would be the correct structure to get the desired behaviour?
Workaround
Currently I found a workaround using two nested beforeEach:
beforeEach(() => {
console.log('beforeEach1')
})
describe('tests', () => {
beforeEach(() => {
console.log('beforeEach2')
})
it('test 1', () => {
console.log('it')
})
})
Which output is:
beforeEach1
beforeEach2
it
I am not sure of this (I have not tested it) but from the doc it seems that your root-level beforeEach probably does not do what you may think.
...
run spec file/s
|
|--------------> per spec file
suite callbacks (e.g., 'describe')
|
'before' root-level pre-hook
|
'before' pre-hook
|
|--------------> per test
'beforeEach' root-level pre-hook
|
'beforeEach' pre-hook
...
From the above picture, you can see that for each describe the before root-level pre-hook is called. Just turn your root-level beforeEach in a before and it should be solved.
The general rule is that before callback always come "before"(no pun intended) the beforeEach callback, independently from the level they are defined in.
You could use a describe() that has a beforeEach() and a describe() inside of it.
Something like this
describe ('A blah component', () =>
{
beforeEach(() => {
console.log('beforeEach')
})
describe(`tests`, () => {
before(() => {
console.log('before')
})
it('test 1, () => {
console.log('it')
})
})
}
Related
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.
My question is, how do you override the variable in the async function which is out of the scope of this?
I read here that the problem is the lack of a callback. After adding the callback, the variable outside the scope in which it was changed (but still the variable itself is in the correct scope) returns "undefined". What am I doing wrong?
Test:
const savedVariableCallback = (variable) =>
console.log(`Variable saved ${variable}`);
describe(() => {
...
it("Sample input type", () => {
let fixValue;
cy.fixture("example.json").then(({ email }) => {
actionsPage
.selectSampleInput()
.then((input) => {
checkAmountOfElements(input, 1);
checkVisiblity(input);
})
.type(email)
.then((input) => {
checkIfValue(input, email);
fixValue = "Nice work";
savedVariableCallback(fixValue);
});
});
cy.log(`fixValue is: ${fixValue}`);
});
})
I expect the first log to show Variable saved Nice work and the second log to show fixValue is: Nice work for variables. But for now, I get in the first log Variable saved Nice work but in second I get undefined.
I want to have that variable to be accessible in it() method scope.
Edit: Since the reference didn`t work I suggest approaching it with an allias
const savedVariableCallback = (variable) =>
console.log(`Variable saved ${variable}`);
describe(() => {
...
it("Sample input type", () => {
cy.fixture("example.json").then(({ email }) => {
actionsPage
.selectSampleInput()
.then((input) => {
checkAmountOfElements(input, 1);
checkVisiblity(input);
})
.type(email)
.then((input) => {
checkIfValue(input, email);
let fixValue = "Nice work";
savedVariableCallback(fixValue);
cy.wrap(fixValue).as('fixValue')
});
});
cy.get('#fixValue')
.then(fixValue => {
cy.log(`fixValue is: ${fixValue.value}`);
})
});
})
If you change
cy.log(`fixValue is: ${fixValue}`)
to
console.log(`fixValue is: ${fixValue}`)
you can see the order of logging
fixValue is: undefined
Variable saved Nice work
so cy.log() grabs the value of fixValue at the time the command is added to the queue, that is before cy.fixture() has run, even though it actually runs after the cy.fixture().
You can defer it until cy.fixture() is complete by adding another .then()
cy.fixture("example.json").then(data => {
...
})
.then(() => {
// enqueued after the fixture code
cy.log(`fixValue is: ${fixValue}`) // now it sees the changed value
})
but of course everything downstream that needs to use fixValue must be inside the .then() callback.
You can also defer the "grabbing" of the value
cy.then(() => cy.log(`fixValue is: ${fixValue}`))
or split cy.fixture() into a before(), which will resolve the variable before the test begins
let fixValue;
before(() => {
cy.fixture("example.json").then(data => {
...
fixValue = ...
})
})
it('tests', () => {
cy.log(`fixValue is: ${fixValue}`) // sees the changed value
})
Currently I have this:
jest.mock('my/hook', () => () => false)
I want my custom React hook module to return false in every test by default, but in a few tests I want it to return true.
The hook is implemented essentially like this:
function useMyHook(key) {
switch (key) {
case 'foo':
case 'bar':
return true
default:
return false
}
}
I am using the hook several times in my component, once for the foo key and once for the bar key. I want it to return false for both keys by default.
But for a few tests I want the foo key to return true, and for other tests I want the bar key to return true.
I tried that by doing this in the specific test, but it didn't do anything:
it('should do x', () => {
jest.doMock('my/hook', () => (key) => {
if (key == 'foo') {
return true
}
})
// ... rest of test
})
How do I customize module mocks on a per-test basis in Jest?
jest.doMock alone can't do anything because a module that depends on it has been already imported earlier. It should be re-imported after that, with module cache discarded with either jest.resetModules or jest.isolateModules:
beforeEach(() => {
jest.resetModules();
});
it('should do x', () => {
jest.doMock('my/hook', ...)
require('module that depends on hook');
// ... rest of test
})
Since it's a function that needs to be mocked differently, a better way is to mock the implementation with Jest spies instead of plain functions:
jest.mock('my/hook', () => jest.fn(() => false))
...
it('should do x', () => {
hook.mockReturnValueOnce(true);
// ... rest of test
})
Is there a shorter way to spy on object methods than the following?
describe('blah blah', () => {
let localStorageSetSpy, localStorageGetSpy;
beforeEach(() => {
localStorageGetSpy = spyOn(window.localStorage, 'getItem');
localStorageSetSpy = spyOn(window.localStorage, 'setItem');
});
it('yada yada', () => {
// blah blah
})
})
It would be nice if I could spy on the getter and setter as part of the same object. I tried reassigning window.localStorage to jasmine.createSpyObj(...) but got complaints about it being readonly.
Any ideas?
If you just want to interact with one object in your test, maybe you can do this
describe('blah blah', () => {
const localStorageSpy = { getItem: undefined, setItem: undefined };
beforeEach(() => {
localStorageSpy.getItem = spyOn(window.localStorage, 'getItem');
localStorageSpy.setItem = spyOn(window.localStorage, 'setItem');
});
it('yada yada', () => {
localStorageSpy.getItem.mockReturnValue(...)
})
})
I would use some methods or functions to wrap around your window.localStorage operations. For example store() (which calls getItem) and retrieve() (which calls setItem).
Some benefits:
You will be able to spy on and mock said functions very easily.
In the future, you will be able to change the behaviour of these functions without having to care about what is done inside as long as the prototypes of store() and retrieve() stay the same.
describe('blah blah', () => {
const localStorageSpy = jasmine.createSpyObj('localStorage',['getItem','setItem']) // Jasmine automatically create a spy for you
beforeEach(() => {
});
it('yada yada', () => {
localStorageSpy.getItem = (() => { return mockReturnValue(...) }) // Do your test here.
})
})
Maybe you can try this. Jasmine createSpyObj will create spy for as well. However, for localStorage it is good that you come out with a mock implmentation.
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");
});