Making assertions on Jest manual mocks - node.js

I'm having some trouble with manual Jest mocks and could use some help.
The file I am testing looks something like this:
import withTranslation from '../utils/withTranslation'
withTranslation('test')
I'm mocking a module in a test file like this.
import withTranslation from '../utils/withTranslation'
jest.mock('../utils/withTranslation')
// tests here
I have the manual mock at ../utils/__mocks__/withTranslation.js with the following code:
const impl = (...args) => {
console.log('in mock', args)
return args
}
export default impl
When the test runs, the mock is used and I can see the console log. So far so good.
However, I want to be able to make assertions on the usage of withTranslation when it is mocked. For example, expect(withTranslation).toHaveBeenCalledWith('test').
So, I change the manual mock to be a Jest mock function with the same implementation as before.
const impl = (...args) => {
console.log('in mock 1', args)
return args
}
// only difference is wrapping in jest.fn()
export default jest.fn(impl)
When the test runs now, a generic Jest mock function is used and I DO NOT see the console log, so my fake implementation is never called. Any ideas?

The code in my question is correct. The reason this was happening is because my test suite had a global beforeEach which was calling jest.resetMocks(). Hopefully this saves someone a few hours.

The function withTranslation in the actual module is already replaced with the mock before the test begins and this replacement is not done via reference. Hence when you assert the mock withTranslation the assertion fails because the mocked function is never called but rather the withTranslation method in the actual module is called.
Your console log still appears because as I said above the "real" module's function has already been replaced with the mock.
So import the actual withTranslation and assert it and it will resolve your problem.
Credit to this answer.

Related

Can I get Jest test instance id or context

Suppose my test setup has the following pattern:
Build a model for this test
Initialize some application based on this model
Test the functioniality in the application
As part of #3, there are some async functions which can be run.
I have thousands of tests and want to create a safeguard against leaks of async functions into other tests- in other words, developers who forget to await for the result
My idea is as follows - Implement an afterEach in a global context which would check if an applcation instance was created for this test. If it was, I can catch all open async functions from here, and throw an error
To do this, I would need to have a testId instance or test context to which I can register the app instance when creating it for tests.
If we were using jasmine and old ES5 syntax, jasmine has a userContext feature which would make this easy:
it('some test', async function(){
this.appInstance = createAppInstance(model)
...
})
afterEach(function(){
if(this.appInstance){
...
}
})
Is there a way to do something like this in jest?

mock functions don't work in .test files after adding setupFilesAfterEnv

After adding setupFilesAfterEnv in the jest.config.js like that:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
setupFilesAfterEnv: ["./test/setupAfterEnv.ts"]
}
the mock functions don't work anymore in the .test.ts files but works in the file that was written inside the setupFilesAfterEnv like ./test/setupAfterEnv.ts
mock function example(the .test.ts file and setupAfterEnv.ts in the same director):
jest.mock("../src/service/sendEmail.ts", () => ({
send: () => {
return "MOCK sendEmail sent"
}
}));
I don't get any errors, and the other function that should be mocked runs. When I write wrong path in the mock function, I get the error can't find module. So it looks it find the function that should be mocked but doesn't run the mock function and run the other function instead
And if I commented the setupFilesAfterEnv config in jest.config.js it works again.
Documentation states
Warning: Importing a module in a setup file
(as specified by setupTestFrameworkScriptFile)
will prevent mocking for the module in question,
as well as all the modules that it imports.
If you have any imports/requires in your ./test/setupAfterEnv.ts, they likely imported other files, and anything chain imported in that process will not be able to be mock('../somemodule'); within tests.
I'm not sure why this restriction is in place; if you call the mock('../somemodule') within the setupAfterEnv.ts file itself, it will be mocked later within your tests. But I consider that bad, as only some tests may want to mock some modules, and others expect them not to be mocked. A pity. In my case, I wanted to import a typeorm library and connect to the database. But the class that creates this connection imports all our entities and all the entity classes import other classes which import other classes etc etc... so nothing becomes mockable. Simply unusable because of this restriction.

TestCafe: import tests from another file into the current fixture

I have a file tests.js that contains some test(...) definitions. I want to reuse these tests across multiple fixtures, preferably without making any modifications to the original code.
So I wrote a main.js that defines a fixture and imports tests.js, thereby "assembling" a test suite. (In case that works, I could write different driver files with different fixtures, importing the same tests.js from within each.)
However, I'm getting a test is not defined error when trying to execute main.js:
C:\Windows\Temp\dummy>testcafe chrome main.js --debug-on-fail
ERROR Cannot prepare tests due to an error.
ReferenceError: test is not defined
at Object.<anonymous> (C:\Windows\Temp\dummy\tests.js:1:1)
at Object.<anonymous> (C:\Windows\Temp\dummy\main.js:7:1)
Type "testcafe -h" for help.
Minimal sample:
// tests.js
test('wait', async t => {
await t.wait(1);
});
// main.js
fixture `here goes the name`
.page("http://localhost:3000")
.beforeEach(async t => {
// do stuff
});
import "./tests";
/*
trick testcafe to scan the file;
based on https://github.com/DevExpress/testcafe/issues/2889#issuecomment-423859785
test();
*/
I already tried:
removing the block comment hack (test();) - which gives ERROR No tests to run. Either the test files contain no tests or the filter function is too restrictive.
moving the tests.js import to the top - still gives test is not defined
importing testcafe from within main.js and tests.js - same error
Is there a way to make the test function "visible" to other files imported by the testcafe entrypoint file? Or will I actually need to modify my tests.js file in order to get this working? Maybe by adding the test definitions into a method, and invoking it from within main.js - as in the original code sample of this issue?
TestCafe doesn't allow calling fixture and test functions outside the test scope. You can wrap your tests from the tests.js file in a function and call this function in the main.js file:
// tests.js
export default function () {
test('Test 1', () => {});
test('Test 2', () => {});
test('Test 3', () => {});
}
// main.js
import defineTests from './tests';
defineTests();
See also: Organize Tests
Try to add the option --disable-test-syntax-validation on the TestCafe command-line
(works only in latest TestCafe version).

How does work Sinon.JS fake.yields?

I just started investigation of Sinon.JS and after some research I stuck with the following two methods and do not know how exactly they are working:
sinon.fake.yields(callback[, value1, ..., valueN]);
and
sinon.fake.yieldsAsync(callback[, value1, ..., valueN]);
It would be great if somebody explain (or even show some example) how they work and when should I use them. One more important question what is the difference between fake, stub and mock in common?
Thanks in advance!
The Sinon docs explain that yields() returns a fake that "expects the last argument to be a callback and will invoke it with the given arguments".
yieldsAsync() does the same thing but invokes the callback asynchronously.
Looking at the Sinon source code makes it clear exactly what this means:
Calling sinon.fake.yields(...) records the arguments passed and returns a function.
When the returned function is invoked it ignores all of its own arguments except the last one which it assumes is a callback. It then invokes that callback with the arguments it recorded from when it was created with yields():
import * as sinon from 'sinon';
const fake = sinon.fake.yields('arguments', 'passed', 'to', 'yields');
const callback = (...args) => {
console.log(args);
}
// logs the following to the console:
// [ 'arguments', 'passed', 'to', 'yields' ]
fake('every argument', 'except the last', 'is ignored', callback);
yieldsAsync() does exactly the same thing except that it invokes the callback asynchronously.
In Sinon, a spy "is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls". It just spies on the calls made to it. It can either wrap nothing or it can wrap and transparently pass through to an existing function.
A stub is a spy "with pre-programmed behavior". A stub lets you spy on the calls made to it and also lets you define how the function should behave (return value, exception thrown, etc.). If an existing function is wrapped in a stub the behavior defined by the stub is used instead of the function.
"fake was introduced with Sinon with v5. It simplifies and merges concepts from spies and stubs." It spies on the calls made to it like both spies and stubs, it can be created with or without behavior and it can also wrap an existing function.
A mock is a fake method "with pre-programmed behavior...as well as pre-programmed expectations". A mock lets you define how the function should behave and also how it is expected to be used. Calling verify() on a mock will fail if the mock was not used as expected.

Changing mocha tests to jest tests

I'm having trouble changing my mocha tests to jest tests.
I have three test files with three classes: FirstTestGroup, SecondTestGroup and ThirdTestGroup, each with a static execute method, that contains some tests, like this:
class FirstTestGroup {
execute(params) {
describe('some tests', function tests() {
it('one test', () => {
// uses params
});
...
});
...
}
}
Each of those execute methods use the same parameters. These parameters are created in an async before call, like shown bellow.
describe('My Tests', function testSuite() {
let params;
before('param creation', async function asyncFunc() {
// creates params asynchronously
});
it('should pass all', () => {
FirstTestGroup.execute(params);
SecondTestGroup.execute(params);
ThirdTestGroup.execute(params);
});
});
The it('should pass all', ...) is needed because everything inside a describe is run instantly, so params would be passed as null without it. This works in mocha because "it"s can be nested, but apparently this is not the case for jest.
I could make the beforeAll (equivalent of before in jest) be called each time before a test group is run, but I didn't want to do that as this seems inefficient.
I could also place the code inside the before call before the describe('My Tests', ...) is defined. This seems wrong as this should be part of the describe only.
I couldn't find anything in the jest documentation that could help me with that.
Am I doing something wrong? Is there a way to achieve this using jest, even if I have to restructure the tests? But I'd like to keep the tests in different files.
This is my first question here, so please tell me if more info is needed as I'm not used writting here.
Have you tried Jest-Codemods yet?
Jest-Codemods allows you to convert your Mocha, AVA, Jasmine tests into equivalent Jest tests. It would also help you migrate assertion libraries such as Chai and Expect.
The way you do it:
Install jest-codemods with npm install -g jest-codemods
Go to your project and execute jest-codemods
It will ask you Which test library would you like to migrate from?
Select Mocha by using arrow keys (As you want to migrate from Mocha)
Next, It will ask you Will you be using Jest on Node.js as your test runner?
Select Yes, use the globals provided by Jest (recommended)
Last, You need to provide file name on which you are working to migrate from Mocha to Jest.
And you are done!
It will automatically migrate your code from Mocha to Jest. No need to touch code. That's one of the most powerful feature of Jest which would save your time and you don't need to worry about changing mocha tests to jest tests.

Resources