Given the following test code:
import { MyParentClass } from "MyParentClass";
import { MyClass } from "MyClass";
MyClass.prototype.post = jest.fn(() => Promise.resolve({token: '12345'}));
it('first test: this test will be successful tested ✓', async() => {
const myParentClass = new MyParentClass();
await expect(myParentClass.run()).toEqual({token: '12345'});
})
it('second test: this test will fail ×', async () => {
MyClass.prototype.post = jest.fn(() => Promise.reject({message: 'An error ocurred'}));
const myParentClass = new MyParentClass();
await expect(myParentClass.run()).rejects.toEqual({message: 'success'});
})
The "run" method internally makes use of the 'MyClass' class and its "post" method. When I run a code like this, for my second test, I get a message like the following:
"Received promise resolved instead of rejected"
I understand that when the answer for the "post" method is globally defined, it will always take that value, but I also understand that in the second test I am overwriting the behavior of that method, why don't you take it into account?
I know I can also use jest.doMock, but I don't understand the documentation well, could someone help me understand it to apply it to my example?
Using asyncFn as so will solve the problem, and additionally simplify the test by making it read in chronological order.
import asyncFn from '#asyncFn/jest';
describe('myParentClass', () => {
let parentClass;
let runPromise;
beforeEach(() => {
parentClass = new MyParentClass();
parentClass.post = asyncFn();
runPromise = myParentClass.run();
});
it('first test: this test will be successful tested ✓', () => {
parentClass.post.resolve({ token: '12345' });
return expect(runPromise).resolves.toEqual({ token: '12345' });
})
it('second test: this test will fail ×', () => {
parentClass.post.reject({ message: 'An error occured'} );
return expect(runPromise).rejects.toEqual({ message: 'An error occured' });
});
});
You can use mockImplementationOnce or mockReturnValueOnce since you've already converted post to a jest mock function. So you could do something like this on your 2nd test:
it('second test: this test will fail ×', async () => {
MyClass.prototype.post.mockReturnValueOnce(Promise.reject({message: 'An error ocurred'}));
const myParentClass = new MyParentClass();
await expect(myParentClass.run()).rejects.toEqual({message: 'An error ocurred'});
})
I'm trying to write a unit test for a Node.js project's logic using Jest.
However, most documentations only provide a case for importing a module or class, however, in my case, my module only contains functions.
So far I know that there are mainly three ways to test a function in Jest:
1) jest.fn()
2) jest.spyOn
3) jest.mock('path')
I have tried all three but none work.
I wanted to test if the function returns a correct value(string) when called.
I tried many different
Here's my code: (I will show short snippets of my code in the later parts)
getDefApCode.ts
export function getDefApCode(code: string) {
switch (code) {
case 'BKK':
return 'NRT'
case 'CTX':
return 'ICN'
case 'SIN':
return 'TPE'
default:
return code
}
}
export function getDefaultDepartureCode(code: string) {
return code ? getDefaultLocationCode(code) : 'LHR'
}
export function getDefaultDestinationCode(code: string) {
return code ? getDefaultLocationCode(code) : 'ZRH'
}
getDefAPCode.spec.ts >> Pattern 1 (using required + jest.fn)
import { Connection, getConnection, getConnectionOptions } from "typeorm";
import { bootstrap, dbConnection } from "../../../src/app";
import { TourSearchParamsFactory } from "../../helpers/typeOrmFactory";
import * as getDefAPCode from "../../../src/controllers/logic/getDefAPCode";
describe("Logic Test", () => {
beforeAll(async () => {
await dbConnection(15, 3000);
});
afterAll(async () => {
const conn = getConnection();
await conn.close();
});
it("should get a default location code", async () => {
const getLocation = require('../../../src/controllers/logic/getDefAPCode');
const code = jest.fn(code => 'BKK');
const getCode = getLocation(code);
expect(getCode).toHaveBeenCalled();
});
});
Error Message:
TypeError: getLocation is not a function
getDefAPCode.spec.ts >> Pattern 2 (using spyON)
import { Connection, getConnection, getConnectionOptions } from "typeorm";
import { bootstrap, dbConnection } from "../../../src/app";
import { TourSearchParamsFactory } from "../../helpers/typeOrmFactory";
import * as getDefaultLocationCode from "../../../src/controllers/logic/getDefaultLocationCode";
describe("Logic Test", () => {
beforeAll(async () => {
await dbConnection(15, 3000);
});
afterAll(async () => {
const conn = getConnection();
await conn.close();
});
const { getDefaultLocationCode, getDefaultDepartureCode, getDefaultDestinationCode } = require('../../../src/controllers/logic/getDefaultLocationCode');
it("should get a default location code", async () => {
const spy = jest.spyOn(getDefaultLocationCode, 'getDefaultLocationCode');
getDefaultLocationCode.getDefaultLocationCode('AKJ');
expect(spy).toHaveBeenCalled();
});
});
These are some error messages appear when I tried a different pattern (I didn't keep track of all of the test code pattern, will add the test code pattern once I fixed docker)
Error Message:
Cannot spy the getDefaultLocationCode property because it is not a function; undefined given instead
31 | const spy = jest.spyOn(getDefaultLocationCode, 'getDefaultLocationCode');
Past Error Messages
error TS2349: This expression is not callable.
Type 'typeof import("/app/src/controllers/logic/getDefAPCode")' has no call signatures.
another one
expect(received).toHaveBeenCalled()
Matcher error: received value must be a mock or spy function
Received has type: string
Received has value: "NRT"
I figured out that I don't have to use mock function in this case.
I stored argument in a variable and then I use the variable instead using a string directly.
Here's how I edit my test code
it("should get a default location code", () => {
const code = 'BKK';
expect(code).toHaveBeenCalled();
});
I got two problems with this jest test:
Is it possible to define the Content collection only once instead of doing that inside of the test?
I do get this error:
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with --detectOpenHandles to troubleshoot this issue.
I don't see why my async code weren't stopped...
import resolvers from 'resolvers/'
import Db from 'lib/db'
const db = new Db()
describe('Resolver', () => {
let token
beforeAll(async () => {
await db.connect()
})
beforeEach(async () => {
token = 'string'
await db.dropDB()
})
afterAll(async () => {
await db.connection.close()
})
describe('articleGetContent()', () => {
test('should return dataset', async () => {
// SETUP
const Content = db.connection.collection('content')
const docs = [{
// some content...
}]
await Content.insertMany(docs)
// EXECUTE
const result = await resolvers.Query.articleGetContent({}, {
id: '123,
language: 'en'
}, {
token
})
// VERIFY
expect.assertions(1)
expect(result).toBeDefined()
})
})
})
resolver
import { articleGetContent } from '../models/article'
export default {
Query: {
articleGetContent: async (obj, { id }, { token }) => articleGetContent(id, token)
}
}
This is how my db class looks like
db.js
export default class Db {
constructor (uri, callback) {
const mongo = process.env.MONGO || 'mongodb://localhost:27017'
this.mongodb = process.env.MONGO_DB || 'testing'
this.gfs = null
this.connection = MongoClient.connect(mongo, { useNewUrlParser: true })
this.connected = false
return this
}
async connect (msg) {
if (!this.connected) {
try {
this.connection = await this.connection
this.connection = this.connection.db(this.mongodb)
this.gfs = new mongo.GridFSBucket(this.connection)
this.connected = true
} catch (err) {
console.error('mongo connection error', err)
}
}
return this
}
async disconnect () {
if (this.connected) {
try {
this.connection = await this.connection.close()
this.connected = false
} catch (err) {
console.error('mongo disconnection error', err)
}
}
}
async dropDB () {
const Content = this.connection.collection('content')
await Content.deleteMany({})
}
}
Related to the second question I hope you've found some issues on github about it.
In general, the issue is described in the debug log.
Jest works with promises, as a result, you shouldn't leave any async operations in any status except resolved.
In your case, you have your DB connection opened so you need to implement another method disconnect for your DB class, this link to docs will help you, but I guess you have it already as it's not the full db.js file ( I see some custom method dropDB. Main idea here is to have it in afterAll hook:
afterAll(() => db.disconnect());
Great example at the bottom of the page
What about the first question, it really depends on what you are doing in your method dropDB. If you're running method for dropping collection, you could store the reference to this collection somewhere outside and use it as it will automatically create the new one, but it would be great to see this method.
Additionally, your async test was created in a wrong way, you could read more here for example in my Update. You need to run this function in the beginning of the test: expect.assertions(number)
expect.assertions(number) verifies that a certain number of assertions
are called during a test. This is often useful when testing
asynchronous code, in order to make sure that assertions in a callback
actually got called.
Trying to mock one of the function with callback from api and getting error as TypeError: specificMockImpl.apply is not a function
import { IEnvironmentMap, load } from 'dotenv-extended';
import { getTokensWithAuthCode, sdk } from '../src/connection-manager';
describe('getTokensWithAuthCode function Tests', () => {
jest.useFakeTimers();
let boxConfig: IEnvironmentMap;
beforeAll(() => {
boxConfig = load({
errorOnMissing: true,
});
});
it('should reject a promise if there is wrong auth code provided', async () => {
sdk.getTokensAuthorizationCodeGrant = jest.fn().mockImplementation(boxConfig.BOX_AUTH_CODE, null, cb => {
cb('Error', null);
});
try {
const tokens = await getTokensWithAuthCode();
} catch (error) {
expect(error).toBe('Error');
}
});
});
And my function which is trying to test is as follow:
import * as BoxSDK from 'box-node-sdk';
import { IEnvironmentMap, load } from 'dotenv-extended';
import {ITokenInfo} from '../typings/box-node-sdk';
const boxConfig: IEnvironmentMap = load({
errorOnMissing: true,
});
export const sdk: BoxSDK = new BoxSDK({
clientID: boxConfig.BOX_CLIENT_ID,
clientSecret: boxConfig.BOX_CLIENT_SECRET,
});
/**
* - Use the provided AUTH_CODE to get the tokens (access + refresh)
* - Handle saving to local file if no external storage is provided.
*/
export async function getTokensWithAuthCode() {
return new Promise((resolve: (tokenInfo: ITokenInfo) => void, reject: (err: Error) => void) => {
if (boxConfig.BOX_AUTH_CODE === '') {
reject(new Error('No Auth Code provided. Please provide auth code as env variable.'));
}
sdk.getTokensAuthorizationCodeGrant(boxConfig.BOX_AUTH_CODE, null, (err: Error, tokenInfo: ITokenInfo) => {
if (err !== null) {
reject(err);
}
resolve(tokenInfo);
});
});
}
Is there any other way to mock function in jest? I have read an article https://www.zhubert.com/blog/2017/04/12/testing-with-jest/
On this line, rather than pass a function to mockImplementation, you're passing three arguments:
jest.fn().mockImplementation(boxConfig.BOX_AUTH_CODE, null, cb => {
cb('Error', null);
});
It looks like you might have just missed some braces. Try switching it to:
jest.fn().mockImplementation((boxConfig.BOX_AUTH_CODE, null, cb) => {
cb('Error', null);
});
It's better not trying to mutate a const used elsewhere.
You could change getTokensWithAuthCode to make it receive sdk as parameter, thus in your test you would pass the mock function as argument, therefore having a more predictable behavior than mutating directly sdk.
In your code, you could make a second getTokensWithAuthCode implementation, with the signature getTokensWithAuthCodeUnbound(sdk) for example, and export it. This implementation will be used in your tests.
Exporting using the same getTokensWithAuthCode name, you would call:
export const getTokensWithAuthCode = getTokensWithAuthCodeUnbound.bind(null, sdk)
That way, your app will use getTokensWithAuthCodeUnbound bound with the default sdk, and you can test more easily its implementation.
Mozilla Developer Network (MDN) bind documentation.
I'm working with some code where I need to test the type of an exception thrown by a function (is it TypeError, ReferenceError, etc.?).
My current testing framework is AVA and I can test it as a second argument t.throws method, like here:
it('should throw Error with message \'UNKNOWN ERROR\' when no params were passed', (t) => {
const error = t.throws(() => {
throwError();
}, TypeError);
t.is(error.message, 'UNKNOWN ERROR');
});
I started rewriting my tests in Jest and couldn't find how to easily do that. Is it even possible?
In Jest you have to pass a function into expect(function).toThrow(<blank or type of error>).
Example:
test("Test description", () => {
const t = () => {
throw new TypeError();
};
expect(t).toThrow(TypeError);
});
Or if you also want to check for error message:
test("Test description", () => {
const t = () => {
throw new TypeError("UNKNOWN ERROR");
};
expect(t).toThrow(TypeError);
expect(t).toThrow("UNKNOWN ERROR");
});
If you need to test an existing function whether it throws with a set of arguments, you have to wrap it inside an anonymous function in expect().
Example:
test("Test description", () => {
expect(() => {http.get(yourUrl, yourCallbackFn)}).toThrow(TypeError);
});
It is a little bit weird, but it works and IMHO is good readable:
it('should throw Error with message \'UNKNOWN ERROR\' when no parameters were passed', () => {
try {
throwError();
// Fail test if above expression doesn't throw anything.
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe("UNKNOWN ERROR");
}
});
The Catch block catches your exception, and then you can test on your raised Error. Strange expect(true).toBe(false); is needed to fail your test if the expected Error will be not thrown. Otherwise, this line is never reachable (Error should be raised before them).
#Kenny Body suggested a better solution which improve a code quality if you use expect.assertions():
it('should throw Error with message \'UNKNOWN ERROR\' when no parameters were passed', () => {
expect.assertions(1);
try {
throwError();
} catch (e) {
expect(e.message).toBe("UNKNOWN ERROR");
}
});
See the original answer with more explanations: How to test the type of a thrown exception in Jest
EDIT 2022:
To use this approach and not trigger no-conditional-expect rule (if you're using eslint-plugin-jest), documentation of this rule suggest to use error wrapper:
class NoErrorThrownError extends Error {}
const getError = async <TError>(call: () => unknown): Promise<TError> => {
try {
await call();
throw new NoErrorThrownError();
} catch (error: unknown) {
return error as TError;
}
};
describe('when the http request fails', () => {
it('includes the status code in the error', async () => {
const error = await getError(async () => makeRequest(url));
// check that the returned error wasn't that no error was thrown
expect(error).not.toBeInstanceOf(NoErrorThrownError);
expect(error).toHaveProperty('statusCode', 404);
});
});
See: no-conditional-expect docs
I use a slightly more concise version:
expect(() => {
// Code block that should throw error
}).toThrow(TypeError) // Or .toThrow('expectedErrorMessage')
From my (albeit limited) exposure to Jest, I have found that expect().toThrow() is suitable if you want to only test an error is thrown of a specific type:
expect(() => functionUnderTest()).toThrow(TypeError);
Or an error is thrown with a specific message:
expect(() => functionUnderTest()).toThrow('Something bad happened!');
If you try to do both, you will get a false positive. For example, if your code throws RangeError('Something bad happened!'), this test will pass:
expect(() => functionUnderTest()).toThrow(new TypeError('Something bad happened!'));
The answer by bodolsog which suggests using a try/catch is close, but rather than expecting true to be false to ensure the expect assertions in the catch are hit, you can instead use expect.assertions(2) at the start of your test where 2 is the number of expected assertions. I feel this more accurately describes the intention of the test.
A full example of testing the type and message of an error:
describe('functionUnderTest', () => {
it('should throw a specific type of error.', () => {
expect.assertions(2);
try {
functionUnderTest();
} catch (error) {
expect(error).toBeInstanceOf(TypeError);
expect(error).toHaveProperty('message', 'Something bad happened!');
}
});
});
If functionUnderTest() does not throw an error, the assertions will be be hit, but the expect.assertions(2) will fail and the test will fail.
I manage to combine some answers and end up with this:
it('should throw', async () => {
await expect(service.methodName('some#email.com', 'unknown')).rejects.toThrow(
HttpException,
);
});
Modern Jest allows you to make more checks on a rejected value. For example, you could test status code of http exception:
const request = Promise.reject({statusCode: 404})
await expect(request).rejects.toMatchObject({ statusCode: 500 });
will fail with error
Error: expect(received).rejects.toMatchObject(expected)
- Expected
+ Received
Object {
- "statusCode": 500,
+ "statusCode": 404,
}
Further to Peter Danis' post, I just wanted to emphasize the part of his solution involving "[passing] a function into expect(function).toThrow(blank or type of error)".
In Jest, when you test for a case where an error should be thrown, within your expect() wrapping of the function under testing, you need to provide one additional arrow function wrapping layer in order for it to work. I.e.
Wrong (but most people's logical approach):
expect(functionUnderTesting();).toThrow(ErrorTypeOrErrorMessage);
Right:
expect(() => { functionUnderTesting(); }).toThrow(ErrorTypeOrErrorMessage);
It's very strange, but it should make the testing run successfully.
In case you are working with Promises:
await expect(Promise.reject(new HttpException('Error message', 402)))
.rejects.toThrowError(HttpException);
You must wrap the code of the function that you are expecting in another arrow function, otherwise the error will not be caught and the assertion will fail.
the function you want to test :
const testThrowingError = () => {
throw new Error();
};
the test:
describe("error function should Throw Error", () => {
expect(() =>testThrowingError()).toThrowError();
});
resource:
https://jestjs.io/docs/expect#tothrowerror
I haven't tried it myself, but I would suggest using Jest's toThrow assertion. So I guess your example would look something like this:
it('should throw Error with message \'UNKNOWN ERROR\' when no parameters were passed', (t) => {
const error = t.throws(() => {
throwError();
}, TypeError);
expect(t).toThrowError('UNKNOWN ERROR');
//or
expect(t).toThrowError(TypeError);
});
Again, I haven't test it, but I think it should work.
Check out toThrow method.
You must wrap the code in an additional function callback!
You should check both: the error message and its type.
For example:
expect(
() => { // additional function wrap
yourCodeToTest();
}
).toThrow(
new RangeError('duplicate prevArray value: A')
);
Because of additional callback wrap, the code will not be run immediately, so jest will be able to catch it.
You should always check the error message to be sure you are checking the correct throw case and not getting another error your code may throw.
It is also nice to check the error type, so the client code may rely on it.
Jest has a method, toThrow(error), to test that a function throws when it is called.
So, in your case you should call it so:
expect(t).toThrowError(TypeError);
The documentation.
I have successfully used this
await expect(
async () => await apiCalls()
).rejects.toThrow();
There's a way to wait an error that comes from a async function, you just have to write your code like in the example bellow
await expect(yourAsyncFunction()).rejects.toThrowError();
The documentation is clear on how to do this. Let's say I have a function that takes two parameters and it will throw an error if one of them is null.
function concatStr(str1, str2) {
const isStr1 = str1 === null
const isStr2 = str2 === null
if(isStr1 || isStr2) {
throw "Parameters can't be null"
}
... // Continue your code
Your test
describe("errors", () => {
it("should error if any is null", () => {
// Notice that the expect has a function that returns the function under test
expect(() => concatStr(null, "test")).toThrow()
})
})
I ended up writing a convenience method for our test-utils library
/**
* Utility method to test for a specific error class and message in Jest
* #param {fn, expectedErrorClass, expectedErrorMessage }
* #example failTest({
fn: () => {
return new MyObject({
param: 'stuff'
})
},
expectedErrorClass: MyError,
expectedErrorMessage: 'stuff not yet implemented'
})
*/
failTest: ({ fn, expectedErrorClass, expectedErrorMessage }) => {
try {
fn()
expect(true).toBeFalsy()
} catch (err) {
let isExpectedErr = err instanceof expectedErrorClass
expect(isExpectedErr).toBeTruthy()
expect(err.message).toBe(expectedErrorMessage)
}
}
A good way is to create custom error classes and mock them. Then you can assert whatever you want.
MessedUpError.ts
type SomeCrazyErrorObject = {
[key: string]: unknown,
}
class MessedUpError extends Error {
private customErrorData: SomeCrazyErrorObject = {};
constructor(err?: string, data?: SomeCrazyErrorObject) {
super(err || 'You messed up');
Object.entries(data ?? {}).forEach(([Key, value]) => {
this.customErrorData[Key] = value;
});
Error.captureStackTrace(this, this.constructor);
}
logMe() {
console.log(this.customErrorData);
}
}
export default MessedUpError;
messedUpError.test.ts
import MessedUpError from './MessedUpError';
jest.mock('./MessedUpError', () => jest.fn().mockImplementation((...args: any[]) => ({
constructor: args,
log: () => {},
})));
type MessedUpErrorContructorParams = Expand<typeof MessedUpError['prototype']>
const MessedUpErrorMock = MessedUpError as unknown as jest.Mock<MessedUpError, [MessedUpErrorContructorParams]>;
const serverErrorContructorCall = (i = 0) => ({
message: MessedUpErrorMock.mock.calls[i][0],
...MessedUpErrorMock.mock.calls[i][1] || {},
});
beforeEach(() => {
MessedUpErrorMock.mockClear();
});
test('Should throw', async () => {
try {
await someFunctionThatShouldThrowMessedUpError();
} catch {} finally {
expect(MessedUpErrorMock).toHaveBeenCalledTimes(1);
const constructorParams = serverErrorContructorCall();
expect(constructorParams).toHaveProperty('message', 'You messed up');
expect(constructorParams).toHaveProperty('customErrorProperty', 'someValue');
}
});
The assertions always go inside the finally clause. This way it will always be asserted. Even if the test does not throw any errors.
There is also an easier way to assert against the error message. The beauty of this method is that you don't need to reconstruct the error object or have the full error message. As long as your error contains part of the error message we can assume it is of the correct type. i.e
const printOnlyString = (str) => {
if(typeof str !== "string"){
throw Error("I can only print strings ${typeof str) given");
}
else {
console.log(str);
}
}
expect(() => printOnlyString(123)).toThrow(/can only print strings/)
Try:
expect(t).rejects.toThrow()