Jest mock function is called but expect.toHaveBeenCalled() fails - javascript

I know that there are already questions about this but I can't find a definite answer. I am using SvelteKit and I tried to mock $app/navigation likes this in setup file.
jest.mock('$app/navigation', () => {
return {
__esModule: true,
goto: jest.fn().mockImplementation((target) => console.log(target))
};
});
I test a component that call goto. It is indeed called because there is a console.log call in the test output. When I tried to test it with expect(goto).toHaveBeenCalled(), it fails.
// SvelteKit
import * as navigations from '$app/navigation';
it('show error when account does not exists', async () => {
// render is in before Each
await fireEvent.change(screen.getByLabelText('Email'), {
target: { value: 'example#email.com' }
});
await fireEvent.change(screen.getByLabelText('Password'), {
target: { value: 'B#adPass0rd' }
});
await fireEvent.click(screen.getByRole('button'));
// There is no problem. It should redirect.
expect(navigations.goto).toHaveBeenCalled();
});
Output
console.log
/success
at log (jest-setup.js:6:58)
FAIL src/lib/routes-tests/login.test.js
Login
✕ show error when account does not exists (23 ms)
● Login › show error when account does not exists
expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
24 | await fireEvent.click(screen.getByRole('button'));
25 | // expect(screen.queryByText('Account does not exist')).not.toBeNull();
> 26 | expect(navigations.goto).toHaveBeenCalled();
| ^
27 | });
28 | });
29 |
at toHaveBeenCalled (src/lib/routes-tests/login.test.js:26:28)
at tryCatch (src/lib/routes-tests/login.test.js:23:2404)
at Generator._invoke (src/lib/routes-tests/login.test.js:23:1964)
at Generator.next (src/lib/routes-tests/login.test.js:23:3255)
at asyncGeneratorStep (src/lib/routes-tests/login.test.js:25:103)
at _next (src/lib/routes-tests/login.test.js:27:194)

It turns out that I called goto in async function. I must use waitFor to expect the change.
await waitFor(() => expect(navigations.goto).toHaveBeenCalled())

Related

Why does Jest rise this error while testing `Colyseus` game?

I am testing a game functionality with Jest as a testing framework and assertion library.
This is my test setup:
const { Server } = require("colyseus")
const { ColyseusTestServer, boot } = require("#colyseus/testing");
const Game = require("./index");
describe("Game", () => {
let colyseus;
let TestServer = new Server()
TestServer.define("my_game", Game)
const User_1 = {
key: "token_header.token_payload.and_oh"
}
beforeAll(async () => colyseus = await boot(TestServer));
afterAll(async () => await colyseus.shutdown());
beforeEach(async () => await colyseus.cleanup());
test("Should connect", async () => {
try {
// `room` is the server-side Room instance reference.
const room = await colyseus.sdk.joinOrCreate("my_game", {...User_1});
await room.waitForNextPatch();
await room.waitForNextMessage();
// `client1` is the client-side `Room` instance reference (same as JavaScript SDK)
// make your assertions
expect(room.sessionId).toEqual(room.clients[0].sessionId);
} catch(err) {
console.log("errr",err)
}
})
})
So every time that I run my tests Jest logs these errors:
Game
✕ Should connect (8003 ms)
● Game › Should connect
TypeError: decode[type] is not a function
at decodePrimitiveType (node_modules/#colyseus/schema/src/Schema.ts:109:34)
at _.Object.<anonymous>.Schema.decode (node_modules/#colyseus/schema/src/Schema.ts:352:25)
at SchemaSerializer.Object.<anonymous>.SchemaSerializer.setState (node_modules/colyseus.js/src/serializer/SchemaSerializer.ts:10:20)
at Room.Object.<anonymous>.Room.setState (node_modules/colyseus.js/src/Room.ts:225:25)
at Room.Object.<anonymous>.Room.onMessageCallback (node_modules/colyseus.js/src/Room.ts:203:18)
at WebSocket.onMessage (node_modules/ws/lib/event-target.js:132:16)
at Receiver.receiverOnMessage (node_modules/ws/lib/websocket.js:983:20)
at Receiver.dataMessage (node_modules/ws/lib/receiver.js:502:14)
at Receiver.getData (node_modules/ws/lib/receiver.js:435:17)
at Receiver.startLoop (node_modules/ws/lib/receiver.js:143:22)
at Receiver._write (node_modules/ws/lib/receiver.js:78:10)
at Socket.socketOnData (node_modules/ws/lib/websocket.js:1077:35)
● Game › Should connect
thrown: "Exceeded timeout of 5000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
18 | beforeEach(async () => await colyseus.cleanup());
19 | jest.setTimeout(8000)
> 20 | test("Should connect", async () => {
| ^
21 | try {
22 | // `room` is the server-side Room instance reference.
23 | const room = await colyseus.sdk.joinOrCreate("my_game", {...User_1});
at server/test.js:20:5
at Object.<anonymous> (server/test.js:7:1)
at TestScheduler.scheduleTests (node_modules/#jest/core/build/TestScheduler.js:333:13)
at runJest (node_modules/#jest/core/build/runJest.js:401:19)
at _run10000 (node_modules/#jest/core/build/cli/index.js:320:7)
at runCLI (node_modules/#jest/core/build/cli/index.js:173:3)
I changed the timeouts for Jest but it did not change anything.
These are versions for Colyseus, Jest, and Node.js which I am using:
"#colyseus/schema": "^1.0.28",
"#colyseus/testing": "^0.14.22",
"colyseus": "^0.14.18",
"jest": "^27.3.1"
node: v14.7
Leaving it there for those struggling with timeout problems (which I had while testing). Mostly it's when no patch is sent after waitForNextPatch() is called, in my case it was either because
nothing has changed
problem at patch sending / serialisation: considering your other issue it's probably was why you had a timeout exception
some weird async situation, calling waitForXXX() too late while the patch has already passed. Unlikely when using in testing situation like this and no real network is involved
This issue was due to an undetermined field type of the synced state's schema of colyseus game framework that had been added to the last versions of the framework. so I just defined the type as playerId: [string] and It resolved without any error.

Unable to properly mock API calls using Jest

I have been trying to mock my apis using jest.mock() but I cant even get jest to call these apis normally.
I have been able to successfully mock the the function and then mock the api like this:
wrapper.vm.getUser = jest.fn(() => Promise.resolve({}));
but I would like to mock all the apis in api/authApis.js directly instead of intercepting them at the function.
I cant do it because this.$apis is showing up as undefined.
How do I access and call this.$apis.getUser() using Jest?
tests/header.spec.js
import { mount } from "#vue/test-utils";
import Header from "../components/Header.vue";
describe("Header", () => {
it("returns the val from api", async () => {
//setting multiUser true so button is visible
const multiUsers = true;
const wrapper = mount(Header, {
propsData: {
multiUsers
}
});
console.log(wrapper.vm.getUser());
const changeUsrBtn = wrapper.find("#btnChangeUser");
//triggering the user btn with a click
changeUsrBtn.trigger("click");
expect(wrapper.find("button").text()).toBe("Change User");
expect(wrapper.vm.getUser()).toHaveReturned();
});
});
The error:
FAIL __tests__/header.spec.js
Header
× returns the val from api (122 ms)
● Header › returns the val from api
expect(received).toHaveReturned()
Matcher error: received value must be a mock function
Received has type: object
Received has value: {}
23 | expect(wrapper.find("button").text()).toBe("Change User");
24 |
> 25 | expect(wrapper.vm.getUser()).toHaveReturned();
| ^
26 |
27 | });
28 | });
at Object.<anonymous> (__tests__/header.spec.js:25:36)
at TestScheduler.scheduleTests (node_modules/#jest/core/build/TestScheduler.js:333:13)
at runJest (node_modules/#jest/core/build/runJest.js:387:19)
at _run10000 (node_modules/#jest/core/build/cli/index.js:408:7)
at runCLI (node_modules/#jest/core/build/cli/index.js:261:3)
console.error
TypeError: Cannot read property 'authService' of undefined
components/Header.vue
async getUser() {
this.showUserLoader = true;
try {
const {
data: { userList }
} = await
this.$apis.authService.getUser();
this.$store.dispatch("auth/updateUsers", UserList);
} catch (error) {
console.error(error);
} finally {
this.showUserLoader = false;
}
}
...
api/authApis.js (I would like to mock this entire file):
export default $axios => ({
getUser() {
return $axios({
url_instance: authInstance,
method: "get",
url_suffix: "/sc/typesOfUser",
});
},
changeUser() {
return $axios({
url_instance: authInstance,
method: "post",
url_suffix: "/sc/changeUser"
});
},
...
})
It depends on how you set the global this.$api, but if you want to mock an entire file (especially using the default export), you should use the __esModule property:
jest.mock('./esModule', () => ({
__esModule: true, // this property makes it work
default: 'mockedDefaultExport',
namedExport: jest.fn(),
}));
More explanations on Jest mock default and named export.

Jest - Testing for AWS Lambda

I'm trying to use Jest with my NodeJS based AWS Lambda function to test that the Lambda handler function is returning specific status codes. It seems like using lambda-tester is the easiest way to achieve this.
handler.test.js
const handler = require('../src/handler.js');
const LambdaTester = require('lambda-tester');
test('should return a StatusCode of 400 due to requirement not being met', function (done) {
return LambdaTester(handler.run)
.event({})
.expectResult(result => {
expect(result.statusCode).toBe(400);
});
});
I thought that would be pretty straightforward however I get this as a result:
expect(received).toBe(expected) // Object.is equality
Expected: 400
Received: 500
7 | .event({})
8 | .expectResult(result => {
> 9 | expect(result.statusCode).toBe(400);
| ^
10 | });
11 | });
12 |
at tests/handler.test.js:9:33
at runVerifier (node_modules/lambda-tester/lib/runner.js:121:16)
at LambdaRunner.run (node_modules/lambda-tester/lib/runner.js:277:23)
If I change the expect line to check for a statuscode of 500 I get an Async timeout:
Timeout - Async callback was not invoked within the 5000 ms timeout
specified by jest.setTimeout.Timeout
Any ideas as to why this isn't working?
The lambda should fail instantly because the event being passed in doesnt have a required attribute and it indeed does when tested via Postman.
Edit:
This lambda is part of a serverless application and could be tested locally, how could I use Jest to do that?
I was over thinking it, the below is working well:
test('should return a StatusCode of 400 due to requirement not being met', async () => {
const event = {};
const context = {};
const result = await handler.run(event, context);
expect(result.statusCode).toBe(400)
expect(result.headers)
});

tryCatch Unit testing with Jest

I'm trying to test tryCatch code with jest but keep on getting error, currently this is what I've tried
student.js
async checkStudentExist(studentName) {
try {
const studentExist = await this.StudentRepository.getOne({name: studentName})
if (studentExist) {
throw new Error(`Student already exist with the name: ${studentName}`)
}
return studentExist
} catch (error) {
throw error
}
}
student.test.js
it('should throw an error if the input name already exist in database', async () => {
mockGetOne.mockReturnValue(Promise.resolve(expectedOrganization))
await studentService.checkStudentExist('john doe')
expect(mockGetOne).toHaveBeenCalledWith({name: 'sample org'})
expect(studentService.checkStudentExist('john doe')).resolves.toThrowError(new Error(`Organization already exist with the name: john doe`))
})
and this is the error I'm getting
FAIL test/specs/services/student_service.test.js
● Console
console.log src/services/student_service.js:53
111
console.log src/services/student_service.js:53
111
● Check if Student exist › should throw an error if the input name already exist in database
Student already exist with the name: john doe
55 | const studentExist = await this.StudentRepository.getOne({name: studentName})
56 | if (studentExist) {
> 57 | throw new Error(`Student already exist with the name: ${studentName}`)
| ^
58 | }
59 | return studentExist
60 | } catch (error) {
at StudentService.checkStudentExist (src/services/student_service.js:57:23)

How to test a unhandledRejection / uncaughtException handler with jest

I have handlers for unhandledRejections and uncaughtExceptions:
bin.js
['unhandledRejection', 'uncaughtException'].forEach(event => {
process.on(event, err => logger.error(err));
});
Now I want to test them with jest:
bin.test.js
const bin = require('../bin');
test('catches unhandled rejections', async () => {
const error = new Error('mock error');
await Promise.reject(error);
expect(logger.error).toHaveBeenCalledWith(error);
});
test('catches uncaught exceptions', () => {
const error = new Error('mock error');
throw error;
expect(logger.error).toHaveBeenCalledWith(error);
});
But jest just tells me that there are errors in the tests:
● catches unhandled rejections
mock error
8 | // https://github.com/facebook/jest/issues/5620
9 | test('catches unhandled rejections', async () => {
> 10 | const error = new Error('mock error');
| ^
11 | await Promise.reject(error);
12 | expect(logger.error).toHaveBeenCalledWith(error);
13 | });
at Object.<anonymous>.test (test/bin.test.js:10:17)
● catches uncaught exceptions
mock error
14 |
15 | test('catches uncaught exceptions', () => {
> 16 | const error = new Error('mock error');
| ^
17 | throw error;
18 | expect(logger.error).toHaveBeenCalledWith(error);
19 | });
at Object.<anonymous>.test (test/bin.test.js:16:17)
is there a way to test this?
This might be related: https://github.com/facebook/jest/issues/5620
My test strategy is to install spy onto process.on() and logger.error methods using jest.spyOn(object, methodName). After doing this, these methods have no side effects. Then, you can test your code logic in an isolated environment.
Besides, there are a few things to note:
You should spy the functions before require('./bin') statement. Because when you load the bin.js module, the code will be executed.
You should use jest.resetModules() in the beforeEach hook to resets the module registry - the cache of all required modules. Why? because require() caches its results. So, the first time a module is required, then its initialization code runs. After that, the cache just returns the value of module.exports without running the initialization code again. But we have two test cases, we want the code in module scope to be executed twice.
Now, here is the example:
bin.js:
const logger = require('./logger');
['unhandledRejection', 'uncaughtException'].forEach((event) => {
process.on(event, (err) => logger.error(err));
});
logger.js:
const logger = console;
module.exports = logger;
bin.test.js:
const logger = require('./logger');
describe('52493145', () => {
beforeEach(() => {
jest.resetModules();
});
afterEach(() => {
jest.restoreAllMocks();
});
test('catches unhandled rejections', () => {
const error = new Error('mock error');
jest.spyOn(process, 'on').mockImplementation((event, handler) => {
if (event === 'unhandledRejection') {
handler(error);
}
});
jest.spyOn(logger, 'error').mockReturnValueOnce();
require('./bin');
expect(process.on).toBeCalledWith('unhandledRejection', expect.any(Function));
expect(logger.error).toHaveBeenCalledWith(error);
});
test('catches uncaught exceptions', () => {
const error = new Error('mock error');
jest.spyOn(process, 'on').mockImplementation((event, handler) => {
if (event === 'uncaughtException') {
handler(error);
}
});
jest.spyOn(logger, 'error').mockReturnValueOnce();
require('./bin');
expect(process.on).toBeCalledWith('uncaughtException', expect.any(Function));
expect(logger.error).toHaveBeenCalledWith(error);
});
});
unit test result:
PASS examples/52493145/bin.test.js
52493145
✓ catches unhandled rejections (5 ms)
✓ catches uncaught exceptions (1 ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
bin.js | 100 | 100 | 100 | 100 |
logger.js | 100 | 100 | 100 | 100 |
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 2.73 s, estimated 4 s
source code: https://github.com/mrdulin/jest-v26-codelab/tree/main/examples/52493145
putting it inside try catch will help:
const error = new Error('mock error');
try {
await Promise.reject(error);
}
catch(error){
expect(logger.error).toHaveBeenCalledWith(error);
}

Categories

Resources