How to add global commands to Jest like describe and it? - javascript

I'm writing a little testing tool for Jest (just to learn). It is called assertTruthy(msg, fn, args), expects a message, a function and arguments and should pass if the thing that is returned when the function is invoked with the arguments is truthy and fail if its not.
I would like to add it to Jest, so that you could call it without importing it in every environment.
describe('someFn()', () => {
// like 'it', 'describe' and 'expect' it should just be available here
it('is a function', () => {
expect(typeop someFN).toEqual('Function');
});
assertTruthy('it should return true', someFn, 3, 4);
});
I know Jest has setupFiles and setupFilesAfterEnv but I can't figure out how to use them to achieve this.
How do you add commands to Jest?
PS: On a single project basis (in CRA) I managed to do this like this:
// in src/setupFiles.js
const assertTruthy = // function definition ...
global.assertTruthy = assertTruthy

Add global functions to Jest
In order to add global functions to jest, you need to define setupFiles in your configuration and attach that function to the global object in the setup file:
module.exports = {
// ...
setupFiles: ['<rootDir>/setupFile.js'],
// ...
};
so if you want to do something very similar to it, I would suggest you do something like this:
// /setupFile.js
// in order to change an existing function (not youre case):
global.it = function(description, fn) { /* ... */ };
// this is how you define a new function globally
global.assertTruthy = (message, func, ...args) => {
return global.it(message, () => {
expect(func(...args)).toBeTruthy();
});
// optional: implementing the same interface as `jest.It`
Supporting the same interface as jest.It
Here's an example from the Airbnb library airbnb/jest-wrap where they wrapped the describe function. If you want to implement the jest.It interface, you'll also need to implement assertTruthy.todo, assertTruthy.skip, assertTruthy.only, & assertTruthy.each (check out the it interface). todo and skip are pretty easy since you want to do exactly the same as the original ones.
For each & only, we need to change the it function inside our implementation. A simple way to support only is by using a closure and passing the correct it function from the closure's inpu. each can be a little more complex to implement.
// /setupFile.js
// normaly, use the jest `it` function
global.assertTruthy = assertTruthyCreator(it);
// bypass for todo and skip
global.assertTruthy.todo = global.it.todo;
global.assertTruthy.skip = global.it.skip;
// only calls the same function but uses `only` internaly
global.assertTruthy.only = assertTruthyCreator(it.only);
// special case which needs special implementation
// see usage below
global.assertTruthy.each = assertTruthyCreator(it.each, true);
function assertTruthyCreator(itFunc, withTable) {
if (withTable) {
return (message, func, ...args) => {
return itFunc(args)(message, (...caseArgs) => {
expect(func(...caseArgs)).toBeTruthy();
});
};
}
return (message, func, ...args) => {
return itFunc(message, () => {
expect(func(...args)).toBeTruthy();
});
};
}
// usage:
assertTruthy.each(
'add numbers',
(a, b) => a + b,
[2, 4],
[4, 5],
[7, 9]);
How to use in test files
If you're using typescript for writing jest test, the first thing you'll need to do is declare your new function somewhere:
interface IGenericFunction {
(...args: any[]): any;
}
declare const assertTruthy: (message: string, func: IGenericFunction, ...args: any[]) => any;
With javascript, you can skip that step.
After that, just use it like you use describe and it:
const funcToTest = (a: number, b: number) => a + b;
describe("Test Suite", () => {
assertTruthy('this ran with assertTruthy', funcToTest, 5, 3);
test("another test", () => {
// ...
});
});
and jest will treat this as any other it function
Using as a node_module dependency
If you want to create a library from this, you can basically just pass a node_modules path to the setupFiles array.
For example, with this repository, you can do as follows:
Install using npm install --save-dev #kibibit/jest-utils
Add the following to your jest configuration
Use the function as described above
module.exports = {
// ...
setupFiles: ['node_modules/#kibibit/jest-utils/lib/jest-utils.js'],
// ...
};
and it should work the same as importing it locally.

Related

How to export from within an imported module's Promise?

I'm trying to work around the fact that I need to import a pure ESM package into a non-module. I can't change that fact about the script.
The workaround I'm trying to use is the import() function (a "dynamic import"). That returns a Promise instead of the actual module. I can't use await since I'm not in a module, so instead I'm using .then().
The pure ESM package (unist-util-visit) is used in a function exported by my script, which is then used in another script. So the import chain goes:
importer.js imports imported.js imports unist-util-visit
So the issue is that anything I export from within the .then() function in imported.js does not show up in importer.js.
And it's not even a timing issue. I used an EventEmitter to make importer.js wait until imported.js's .then() is done executing:
imported.js:
const EventEmitter = require('events');
module.exports.emitter = new EventEmitter();
module.exports.outsideFxn = function () {
console.log('hello');
}
import('unist-util-visit').then((unistUtilVisit) => {
module.exports.fxn = function() {
console.log(`unistUtilVisit: ${typeof unistUtilVisit}`);
}
module.exports.emitter.emit('ready');
});
importer.js:
import('./imported.js').then((imported) => {
console.log("In importer.js's .then():");
console.log(' fxn:', imported.fxn);
console.log(' outsideFxn:', imported.outsideFxn);
imported.emitter.on('ready', () => {
console.log("After imported.js is done:")
console.log(' fxn:', imported.fxn);
});
});
When I execute it, this is the output:
$ node importer.js
In importer.js's .then():
fxn: undefined
outsideFxn: [Function (anonymous)]
After imported.js is done:
fxn: undefined
What am I missing? Why are no exports being defined in the .then() function? How can I get my function exported?
Instead of
import('unist-util-visit').then((unistUtilVisit) => {
module.exports.fxn = function() {
console.log(`unistUtilVisit: ${typeof unistUtilVisit}`);
}
module.exports.emitter.emit('ready');
});
where you attempt to modify your module's exports after it has probably been consumed by dependents, why not export a promise that yields the function when it completes?
module.exports.fxnP =
import('unist-util-visit')
.then((unistUtilVisit) => () => {
console.log(`unistUtilVisit: ${typeof unistUtilVisit}`);
});
Now you consume it:
import('./imported.js').then((imported) => {
imported.fxnP.then((fxn) => {
fxn();
});
});
or, more neatly::
import('./imported.js')
.then(({fxnP}) => fxnP)
.then((fxn) => fxn());
You could use a custom require hook like jiti which can do exactly what you want synchronously.
Without hooking require:
$ node test.cjs
// test.cjs
const jiti = require('jiti')();
const unistUtilVisit = jiti("unist-util-visit");
Hooking require programmatically:
$ node test.cjs
// test.cjs
require('jiti')().register();
const unistUtilVisit = require("unist-util-visit");
Hooking require through a command line option:
$ node -r jiti/register test.cjs
// test.cjs
const unistUtilVisit = require("unist-util-visit");

Accept argument in node_module require

I am creating a node module and I am hoping to force the user to pass an argument during module require.
Like this:
const sample = require('sample-module')('username')
Inside my sample-module, I have declared the public functions like this (these are imports from other files):
module.exports = {
addNewFolder: folderHandler.addNewFolder,
printToFile: printHandler.printToFile,
deleteFolder: folderHandler.deleteFolder
// more functions
}
// Here I want to add a function that will accept the argument passed.
function setUserName(name) {
// some handling here, in this case, 'name' is 'username' as we passed in during require
}
How can I do this?
Lastly, I prefer to require using like this:
const sample = require('sample-module')('username')
Rather than:
const sample = require('sample-module')
sample.setUserName('username')
Help anyone? Thanks!
You can change module.exports to function like this
const test = (arg) => {
console.log(arg);
};
module.exports = function (requireArg) {
return {
test: test(requireArg),
};
};
then require it
const { test } = require('./test')('apple');

Unit Testing ES6 Class with External Dependency

I'm trying to set a unit testing boilerplate for my company. Our front end projects are built with ES6 classes and have a dependency to our core product. The front end code gets wrapped through a build process in a whole other block of code that is basically a closure and captures the dependency. So we don't have to manually import it in order to use it.
Let's say the dependency is called productScope and it's an object that has some DOM models, internal APIs and parameters among many other things necessary for each project. At the moment, Mocha throws ReferenceError: productScope is not defined. How can I mock this object? Or should I just use the actual object?
Example:
class someClass {
constructor() {
const id = productScope.items[0].id
const item = productScope.domModel.querySelector('.some-div')
item.classList.add(`added-${id}`)
}
}
This get wrapped in core code like below:
(function(productScope) {
// front end code goes here
}(productScope)
Testing file:
import someClass from '../../js/someClass'
describe('someClass', function() {
const someClass = new someClass()
it('should be a class', function() {
console.log(someClass)
});
});
You can try something like this
describe('#someClass', () => {
let someClass;
beforeEach(() => {
global.productScope = {
// mocking productScope object
};
});
it('should be a class', () => {
someClass = new SomeClass;
console.log(someClass);
});
afterEach(() => {
delete global.productScope;
});
});
or alternatively if you want more specific mock logic for each test case
describe('#someClass', () => {
let someClass;
it('should be a class', () => {
global.productScope = {
// mocking productScope object
};
// Test logic start
someClass = new SomeClass;
console.log(someClass);
// Test logic end
delete global.productScope;
});
});
Looks like productScope is a global variable.
Something like this should work for you.
import someClass from '../../js/someClass';
describe('someClass', function() {
let someClass;
beforeEach(() => {
global.productScope = {
// you mock definition
someClass = new someClass();
};
});
it('should be a class', function() {
console.log(someClass)
});
});
I'm with other answers as well, as managing global variables seems to be the simplest and most straightforward solution.
However, you can use toString to get class's string representation, and eval it to bind to closure's scope:
class someClass {
constructor() {
this.id = scopedId
}
}
// pass class as an argument
function scopeFactory(classDef) {
// define scoped data
let scopedId = 2;
// eval is used to bind class to the local closure
// so `scopedId` will be in charge
return eval("(" + classDef + ")");
}
const scopedSomeClass = scopeFactory(someClass);
console.log(new scopedSomeClass)
Note that eval(someCLass.toString()) doesn't work without parentheses.
You can add it as a helper function, into your project.

Mocking a function jest but jest calling original function

I have a function that returns true or false, lets call it myFunc
myFunc (){
if(something){return true}
else{return false}
}
that's what it does for sake of arg
I then call it somewhere else
if(myFunc()){
}else{
}
when I log it out, it continually comes out as false. however, when i have mocked it in my test like so:
const myMock = (myModule.myFunc = jest.fn())
myMock.mockReturnValue(true)
so why is it still coming back as false when I log it from the index file? or is that not quite how mocking works?
I'm guessing that myModule is the object you imported, and then you set the mock function on that object. But in the myModule file you are referencing that function directly, not through a module reference, right?
The proper way would probably be to move myFunc out of myModule. But if you want to keep it there, then you are going to have to partially mock myModule:
jest.mock('./myModule', () => {
return {
...jest.requireActual('./myModule'),
myFunc: jest.fn()
}
})
But seriously consider moving myFunc out of myModule, because partial mocking is difficult and confusing.
One way I found to solve my issue was to use a class instead.
Here is a sudo example:
Implementation
export class Location {
getLocation() {
const environment = this.getEnvironmentVariable();
return environment === "1" ? "USA" : "GLOBAL";
}
getEnvironmentVariable() {
return process.env.REACT_APP_LOCATION;
}
}
Test
import { Location } from "./config";
test('location', () => {
const config = new Location();
jest.spyOn(config, "getEnvironmentVariable").mockReturnValue("1")
const location = config.getLocation();
expect(location).toBe("USA");
});

Mock dependency in Jest with TypeScript

When testing a module that has a dependency in a different file and assigning that module to be a jest.mock, TypeScript gives an error that the method mockReturnThisOnce (or any other jest.mock method) does not exist on the dependency, this is because it is previously typed.
What is the proper way to get TypeScript to inherit the types from jest.mock?
Here is a quick example.
Dependency
const myDep = (name: string) => name;
export default myDep;
test.ts
import * as dep from '../depenendency';
jest.mock('../dependency');
it('should do what I need', () => {
//this throws ts error
// Property mockReturnValueOnce does not exist on type (name: string)....
dep.default.mockReturnValueOnce('return')
}
I feel like this is a very common use case and not sure how to properly type this.
You can use type casting and your test.ts should look like this:
import * as dep from '../dependency';
jest.mock('../dependency');
const mockedDependency = <jest.Mock<typeof dep.default>>dep.default;
it('should do what I need', () => {
//this throws ts error
// Property mockReturnValueOnce does not exist on type (name: string)....
mockedDependency.mockReturnValueOnce('return');
});
TS transpiler is not aware that jest.mock('../dependency'); changes type of dep thus you have to use type casting. As imported dep is not a type definition you have to get its type with typeof dep.default.
Here are some other useful patterns I've found during my work with Jest and TS
When imported element is a class then you don't have to use typeof for example:
import { SomeClass } from './SomeClass';
jest.mock('./SomeClass');
const mockedClass = <jest.Mock<SomeClass>>SomeClass;
This solution is also useful when you have to mock some node native modules:
import { existsSync } from 'fs';
jest.mock('fs');
const mockedExistsSync = <jest.Mock<typeof existsSync>>existsSync;
In case you don't want to use jest automatic mock and prefer create manual one
import TestedClass from './TestedClass';
import TestedClassDependency from './TestedClassDependency';
const testedClassDependencyMock = jest.fn<TestedClassDependency>(() => ({
// implementation
}));
it('Should throw an error when calling playSomethingCool', () => {
const testedClass = new TestedClass(testedClassDependencyMock());
});
testedClassDependencyMock() creates mocked object instance
TestedClassDependency can be either class or type or interface
Use the mocked helper
like explained here
// foo.spec.ts
import { foo } from './foo'
jest.mock('./foo')
// here the whole foo var is mocked deeply
const mockedFoo = jest.mocked(foo, true)
test('deep', () => {
// there will be no TS error here, and you'll have completion in modern IDEs
mockedFoo.a.b.c.hello('me')
// same here
expect(mockedFoo.a.b.c.hello.mock.calls).toHaveLength(1)
})
test('direct', () => {
foo.name()
// here only foo.name is mocked (or its methods if it's an object)
expect(jest.mocked(foo.name).mock.calls).toHaveLength(1)
})
There are two solutions tested for TypeScript version 3.x and 4.x, both are casting desired function
1) Use jest.MockedFunction
import * as dep from './dependency';
jest.mock('./dependency');
const mockMyFunction = dep.myFunction as jest.MockedFunction<typeof dep.myFunction>;
2) Use jest.Mock
import * as dep from './dependency';
jest.mock('./dependency');
const mockMyFunction = dep.default as jest.Mock;
There is no difference between these two solutions. The second one is shorter and I would therefore suggest using that one.
Both casting solutions allows to call any jest mock function on mockMyFunction like mockReturnValue or mockResolvedValue
https://jestjs.io/docs/en/mock-function-api.html
mockMyFunction.mockReturnValue('value');
mockMyFunction can be used normally for expect
expect(mockMyFunction).toHaveBeenCalledTimes(1);
I use the pattern from #types/jest/index.d.ts just above the type def for Mocked (line 515):
import { Api } from "../api";
jest.mock("../api");
const myApi: jest.Mocked<Api> = new Api() as any;
myApi.myApiMethod.mockImplementation(() => "test");
Cast as jest.Mock
Simply casting the function to jest.Mock should do the trick:
(dep.default as jest.Mock).mockReturnValueOnce('return')
Use as jest.Mock and nothing else
The most concise way of mocking a module exported as default in ts-jest that I can think of really boils down to casting the module as jest.Mock.
Code:
import myDep from '../dependency' // No `* as` here
jest.mock('../dependency')
it('does what I need', () => {
// Only diff with pure JavaScript is the presence of `as jest.Mock`
(myDep as jest.Mock).mockReturnValueOnce('return')
// Call function that calls the mocked module here
// Notice there's no reference to `.default` below
expect(myDep).toHaveBeenCalled()
})
Benefits:
does not require referring to the default property anywhere in the test code - you reference the actual exported function name instead,
you can use the same technique for mocking named exports,
no * as in the import statement,
no complex casting using the typeof keyword,
no extra dependencies like mocked.
The latest jest allows you to do this very easily with jest.mocked
import * as dep from '../dependency';
jest.mock('../dependency');
const mockedDependency = jest.mocked(dep);
it('should do what I need', () => {
mockedDependency.mockReturnValueOnce('return');
});
Here's what I did with jest#24.8.0 and ts-jest#24.0.2:
source:
class OAuth {
static isLogIn() {
// return true/false;
}
static getOAuthService() {
// ...
}
}
test:
import { OAuth } from '../src/to/the/OAuth'
jest.mock('../src/utils/OAuth', () => ({
OAuth: class {
public static getOAuthService() {
return {
getAuthorizationUrl() {
return '';
}
};
}
}
}));
describe('createMeeting', () => {
test('should call conferenceLoginBuild when not login', () => {
OAuth.isLogIn = jest.fn().mockImplementationOnce(() => {
return false;
});
// Other tests
});
});
This is how to mock a non-default class and it's static methods:
jest.mock('../src/to/the/OAuth', () => ({
OAuth: class {
public static getOAuthService() {
return {
getAuthorizationUrl() {
return '';
}
};
}
}
}));
Here should be some type conversion from the type of your class to jest.MockedClass or something like that. But it always ends up with errors. So I just used it directly, and it worked.
test('Some test', () => {
OAuth.isLogIn = jest.fn().mockImplementationOnce(() => {
return false;
});
});
But, if it's a function, you can mock it and do the type conversation.
jest.mock('../src/to/the/Conference', () => ({
conferenceSuccessDataBuild: jest.fn(),
conferenceLoginBuild: jest.fn()
}));
const mockedConferenceLoginBuild = conferenceLoginBuild as
jest.MockedFunction<
typeof conferenceLoginBuild
>;
const mockedConferenceSuccessDataBuild = conferenceSuccessDataBuild as
jest.MockedFunction<
typeof conferenceSuccessDataBuild
>;
As of Jest 24.9.0 here is how you can mock and correctly type both your Class/Object/function and Jest properties.
jest.MockedFunction
jest.MockedClass
What we would like for a typed mock is that the mocked object type contains the union of the mocked object type and the type of Jest mocks.
import foo from 'foo';
jest.mock('foo');
const mockedFoo = foo as jest.MockedFunction<typeof foo>;
// or: const mockedFooClass = foo as jest.MockedClass<typeof FooClass>;
mockedFoo.mockResolvedValue('mockResult');
// Or:
(mockedFoo.getSomething as jest.MockedFunction<typeof mockedFoo.getSomething>).mockResolvedValue('mockResult');
As you can see, you can either manually cast what you need or you'll need something to traverse all foo's properties/methods to type/cast everything.
To do that (deep mock types) you can use jest.mocked() introduced in Jest 27.4.0
import foo from 'foo';
jest.mock('foo');
const mockedFoo = jest.mocked(foo, true);
mockedFoo.mockImplementation() // correctly typed
mockedFoo.getSomething.mockImplementation() // also correctly typed
I have found this in #types/jest:
/**
* Wrap a function with mock definitions
*
* #example
*
* import { myFunction } from "./library";
* jest.mock("./library");
*
* const mockMyFunction = myFunction as jest.MockedFunction<typeof myFunction>;
* expect(mockMyFunction.mock.calls[0][0]).toBe(42);
*/
Note: When you do const mockMyFunction = myFunction and then something like mockFunction.mockReturnValue('foo'), you're a changing myFunction as well.
Source: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/jest/index.d.ts#L1089
The top rated solution by Artur Górski does not work with the last TS and Jest.
Use MockedClass
import SoundPlayer from '../sound-player';
jest.mock('../sound-player'); // SoundPlayer is now a mock constructor
const SoundPlayerMock = SoundPlayer as jest.MockedClass<typeof SoundPlayer>;
A recent library solves this problem with a babel plugin: https://github.com/userlike/joke
Example:
import { mock, mockSome } from 'userlike/joke';
const dep = mock(import('./dependency'));
// You can partially mock a module too, completely typesafe!
// thisIsAMock has mock related methods
// thisIsReal does not have mock related methods
const { thisIsAMock, thisIsReal } = mockSome(import('./dependency2'), () => ({
thisIsAMock: jest.fn()
}));
it('should do what I need', () => {
dep.mockReturnValueOnce('return');
}
Be aware that dep and mockReturnValueOnce are fully type safe. On top, tsserver is aware that depencency was imported and was assigned to dep so all automatic refactorings that tsserver supports will work too.
Note: I maintain the library.
This is ugly, and in fact getting away from this ugliness is why I even looked at this question, but to get strong typing from a module mock, you can do something like this:
const myDep = (require('./dependency') as import('./__mocks__/dependency')).default;
jest.mock('./dependency');
Make sure you require './dependency' rather than the mock directly, or you will get two different instantiations.
For me this was enough:
let itemQ: queueItemType
jest.mock('../dependency/queue', () => {
return {
add: async (item: queueItemType, ..._args: any) => {
// then we can use the item that would be pushed to the queue in our tests
itemQ = item
return new Promise(resolve => {
resolve('Mocked')
})
},
}
})
Then, whenever the add method is called it will execute this code above instead of pushing it to the queue, in this case.
With TypeScript 2.8 we can do like this with ReturnType:
import * as dep from "./depenendency"
jest.mock("./dependency")
const mockedDependency = <jest.Mock<ReturnType<typeof dep.default>>>dep.default
it("should do what I need", () => {
mockedDependency.mockReturnValueOnce("return")
})

Categories

Resources