jest testing function call inside another function - javascript

Is it possible to test startServer() call once launcApp is called?
import { Server } from './Server/Server';
export class Launcher {
private server: Server;
public constructor() {
this.server = new Server();
}
public launchApp() {
this.server.startServer();
}
}
I tried to do
Server.prototype.startServer = jest.fn();
new Launcher.launchApp();
expect(Server.prototype.startServer).toBeCalled();
but it fails

Your approach worked for me. The only problem with your snippet was that you are not instantiating your Launcher properly. It needs to be:
Server.prototype.startServer = jest.fn();
new Launcher(). launchApp();
expect(Server.prototype.startServer).toBeCalled();
But if you are looking for other ways to mock methods in classes, there are many to achieve it, see: Jest docs: The 4 ways to create an ES6 class mock. One of the ways is described under: https://jestjs.io/docs/es6-class-mocks#mocking-non-default-class-exports
So you can use jest.mock() with your module path, then return an object with the key that is the same as the class export name.
const mockStartServer = jest.fn();
jest.mock("./Server/Server", () => {
return {
Server: jest.fn().mockImplementation(() => {
return { startServer: mockStartServer };
}),
};
});
Note that mockStartServer is defined and initialized with jest.fn() on the outer scope, and outside the returned object, and that's because Jest's mock behavior:
"Since calls to jest.mock() are hoisted to the top of the file, Jest
prevents access to out-of-scope variables. By default, you cannot
first define a variable and then use it in the factory. Jest will
disable this check for variables that start with the word mock.
However, it is still up to you to guarantee that they will be
initialized on time. Be aware of Temporal Dead Zone". src: jest documentation
Then the test would be:
import { Launcher } from "./luncher";
import { Server } from "./Server/Server";
const mockStartServer = jest.fn();
jest.mock("./Server/Server", () => {
return {
Server: jest.fn().mockImplementation(() => {
return { startServer: mockStartServer };
}),
};
});
it("should check for server start", () => {
const luncher = new Launcher();
luncher.launchApp();
expect(mockStartServer).toHaveBeenCalled();
});
You can also create a mock with spyOn, by overriding the implementation of the spied Server.prototype.startServer method.
const startServerMock = jest
.spyOn(Server.prototype, "startServer")
.mockImplementation(() => {});
it("should check for server start", () => {
const lun = new Launcher();
lun.launchApp();
expect(startServerMock).toHaveBeenCalled();
});

Related

mock a service with Jest

Trying to write a test to provide code coverage for the following code :
note : there are other functions in the service but just listing one for brevity.
export const service = {
getById: async (id) => {
const url = `/api/customers/${id}/names`
const {data} = await axios.get(url, axiosOptions);
return data;
}
I'm attempting to simply provide code coverage with this test:
note : I have attempted to use require instead of import but that does not seem to work.
import {service} from './requests';
it("mocks the getById function", () => {
service.getById = jest.fn();
expect(service.getById.mock).toBeTruthy();
}
This test passes however seems to provide no code coverage.
I've attempted to mock out the axios call but I seem to get nowhere as examples I've found of implementations are not working for me currently.
Does anyone have ideas and an example how I could provide code coverage for the service please?
Update : to sonEtLumiere's answer
jest.mock('./service', () => ({
getById: jest.fn().mockResolvedValue({ data : "hello"}),
}));
describe('test', () => {
it('mocks the service", async () => {
service.getById.mockResolvedValue({data: "hello});
const data = await service.getById(1);
expect(data).toEqual({data:"hello"});
})
})
Currently getting back error :
Cannot read properties of undefined (reading 'getById')
Any thoughts on why I'm getting this error?
To mock a service using Jest, you can use the jest.mock() function to create a mocked version of the service. For example:
jest.mock('path/to/service', () => ({
getById: jest.fn().mockResolvedValue({ /* mocked data */ }),
}));
Then, in your test file, you can import the mocked version of the service and use the mock property on the function to control its behavior. For example, you can use .mockResolvedValue to set the resolved value of the function, or use .mockRejectedValue to make the function throw an error.
import { service } from 'path/to/service';
describe('test', () => {
it('mocks the service', async () => {
service.getById.mockResolvedValue({ /* mocked data */ });
const data = await service.getById(1);
expect(data).toEqual({ /* mocked data */ });
});
});
I do agree with #Lin Du's comment, if you want to test service.getById, you should be mocking what the method depends on, in this case axios.get.
But following along with your question, the issue is that the named export in ./requests is an object containing the getById property which is the method you want to test. So jest.mock should look like:
jest.mock("./requests.js", () => ({
service: {
getById: jest.fn(),
},
}))
Then your test will pass as you expected:
it("mocks the getById function", async () => {
service.getById.mockResolvedValueOnce({ data: "hello" })
const data = await service.getById(1)
expect(data).toEqual({ data: "hello" })
})
But again, if you want to test a method and have proper coverage, what you need to mock is the method's dependency, not the method itself, e.g:
import { service } from "./requests"
import axios from "axios"
jest.mock("axios")
test("service.getById", async () => {
axios.get.mockResolvedValueOnce({ data: "hello" })
const result = await service.getById(1)
expect(result).toBe("hello")
})

Jest - Mock a require thats inside a function

The function below is custom code we provide to a third party, hence the weird syntax.
const assignNameFunction = (exports.onExecutePostLogin = async (
event
) => {
const package = require("foo#2.23.0").Client;
package.doThing(event.bar)
});
I need to mock the package to unit test, one of the issues is the version. Due to the third party, I have to specify the version.
I tried the a few variants of:
import { thirdPartyFunction } from "./";
const mockFunc = jest.fn();
jest.mock("foo#2.23.0", ({
Client: {
doThing: mockFunc
}
}));
describe("Third Party Function", () => {
it("doThing is called", async () => {
const event = { bar: "foo" };
await thirdPartyFunction(event)
expect(mockFunc).toBeCalled();
});
});
or instead of mock using
import all from "foo";
jest.spyOn(all, "Client").mockReturnValue(...);
But jest comes back with Cannot find module 'foo#2.23.0'
How can I mock the require from within the function?

How can i create fake functions to test the components?

I have to test my component, but that component have a function for that it can work. This function need a key environment.
My component has a onMounted function
onMounted(async () => {
const available = await getAvailableBills()
}
and the implementation is:
export const getAvailableBills = async () => {
try {
const bill = `${process.env.VAR_ENV}`
I am getting this error:
Error: Provided address {VAR_ENV} is invalid
But I Think I don't need to execute the real functions. I would like to create fake functions in my test
import { shallowMount } from '#vue/test-utils'
import { createApp } from 'vue'
import { createStore } from 'vuex'
import App from '#/components/tables'
const store = createStore({
state() {
return {
user: {},
}
},
mutations: {},
})
let wrapper
const app = createApp(App)
app.use(store)
beforeEach(() => {
wrapper = shallowMount(App, {
propsData: {
whitelist: [1,2,3],
},
global: {
plugins: [store],
},
})
})
describe('Tables', () => {
it('test 1', () => {
expect(wrapper.props().list).toEqual([1,2,3])
})
})
You have to mock it. It seems like you're using jest. You can use jest.spyOn method to mock a particular object, or mock the whole file using jest.mock.
For example, if you have
src /
- app.js
- app.spec.js
- services /
--- bills.js
In your src/app.spec.js, if you're exported function getAvailableBills is in src/services/bills.js, just do this :
import { shallowMount } from '#vue/test-utils'
import { createApp } from 'vue'
import { createStore } from 'vuex'
import App from '#/components/tables'
import { getAvailableBills } from './services/bills'
jest.mock('./services/bills', {
getAvailableBills: jest.fn().mockResolvedValue({ bills: ['...'] }) // you can mock the response directly here for the whole file
})
getAvailableBills.mockResolvedValue({ bills: ['...'] }) // or you can mock the response when you need to mock it
const store = createStore({
state() {
return {
user: {},
}
},
mutations: {},
})
let wrapper
const app = createApp(App)
app.use(store)
beforeEach(() => {
wrapper = shallowMount(App, {
propsData: {
whitelist: [1,2,3],
},
global: {
plugins: [store],
},
})
})
describe('Tables', () => {
it('test 1', () => {
expect(wrapper.props().list).toEqual([1,2,3])
})
})
Notice that I used mockResolvedValue because it's returning a Promise (async method), but if it returns a direct value and not a promise, use mockReturnValue. You can also mock only once when needed with mockResolvedValueOnce and mockReturnValueOnce, or mock a rejected promise with mockRejectedValue.
Btw, you better encapsulate the tests inside describe, it avoids some errors, and it makes more readable & writable tests. You should also mock inside beforeAll / beforeEach methods to mock once for multiple tests when needed.
If needed, you can also add a setup files where you instantiate fake env values inside jest.config. That's not the point but it may help you one day.
If you're trying to replace a function/method of your component, the short answer is: you don't!.
Never replace parts of your component in tests. The tests are there to check if your component fails, now and in the future, after you make changes to it. If you introduce a breaking change in the future, but the test replaces the part/method where you introduce the change, how will your test know the app is broken?
There are a few important principles you need to follow when testing:
Do not modify your component so it passes tests. Only modify it to fix real problems with it, found during testing.
Never replace parts of your component during testing. What you can (and should) do is mock anything external to it, replacing those parts with functions/modules performing what you expect those parts to do in a live environment 1. The gist of a mock is:
jest.mock('some-module', () => jest.fn().mockImplementation(() => yourMock )).
Full docs here.
Mocking needs to happen before your test calls import Something from 'some-module'.
Only test inputs/outputs of your component. Don't test how your component does it (a.k.a internal implementation) (this allows you to refactor it at some later point without breaking the test, as long as your component still outputs as expected for each provided input).
Also don't test if external stuff works. Your test is not concerned with that. External stuff has its own tests and is, typically, already tested. That's why you rely on it.
So, in your case, you want to modify process.env during the test. The only problem is process.env is global and if you make changes to it, those changes will persist after your test ends, unless you change everything back to how it was when you started the test.
Most importantly, you have to call jest.resetModules() before each test, to clear jest cache (by default, jest caches, so it runs tests faster).
Here's an example of how to test with controlled process.env. This should eliminate the need to replace the components' method:
describe('SomeComponent', () => {
const REAL_ENV = process.env;
beforeEach(() => {
jest.resetModules();
process.env = { ...process.env }; // copy, for your test
});
afterAll(() => {
process.env = REAL_ENV; // restore initial values
});
it('should do stuff depending on process.env', () => {
process.env.FOO = 'bar';
wrapper = shallowMount(/*...*/);
expect(/* wrapper to do stuff...*/)
})
});
Testing with controlled process.env values needs shallow mounting inside the test, rather than the typical shallow mounting in beforeAll, I normally create two separate testing suites: one for normal tests, shallow mounting in beforeAll, and one for special tests, which set up environment vars before shallow mounting:
describe('SomeComponent - normal tests', () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(/*...*/)
});
it('should do stuff', () => {
// component here has already been shallowMounted
})
});
describe('SomeCompoent - process.env tests', () => {
const REAL_ENV = process.env;
let wrapper;
beforeEach(/* ... */); // replace process.env
afterEach(/* ... */); // restore process.env
it('Does stuff with FOO = "bar"', () => {
process.env.FOO = 'bar';
wrapper = shallowMount(/* ... */);
expect(/* wrapper to do stuff when process.env.FOO is 'bar' */)
})
it('Does stuff with FOO = "baz"', () => {
process.env.FOO = 'baz';
wrapper = shallowMount(/* ... */);
expect(/* wrapper to do stuff when process.env.FOO is 'baz' */)
})
})
1 As an example, you don't want to use axios in your tests. You don't want to test if the local machine is connected to the internet or if the servers your app is calling are actually up and running. You want a replacement for axios which does what axios is doing without making actual calls, allowing you to control the responses and errors returned, thus testing your component makes the correct calls and behaves as expected when receiving responses/errors from axios.

Trouble mocking ES6 Module using jest.unstable_mockModule

I'm attempting to mock a call to a class instance function on an ES6 module being imported by the code under test. I've followed the progress of the ES6 support and eventually stumbled onto this PR https://github.com/facebook/jest/pull/10976 which mentioned that in 27.1.1 support for jest.unstable_mockModule was added. I upgraded my version of Jest to take advantage and while the test doesn't error, it also doesn't seem to actually mock the module either.
This is the module under test:
// src/Main.mjs
import Responder from './Responder.mjs'
import Notifier from './Notifier.mjs'
export default {
async fetch(request, environment, context) {
let response
try {
response = new Responder(request, environment, context).respond()
} catch (error) {
return new Notifier().notify(error)
}
return response
}
}
Here is the test:
// test/Main.test.mjs
import { jest } from '#jest/globals'
import main from '../src/Main.mjs'
describe('fetch', () => {
test('Notifies on error', async () => {
const mockNotify = jest.fn();
jest.unstable_mockModule('../src/Notifier.mjs', () => ({
notify: mockNotify
}))
const notifierMock = await import('../src/Notifier.mjs');
await main.fetch(null, null, null)
expect(mockNotify).toHaveBeenCalled()
})
})
I'm trying to mock the call to Notify to expect it to have been called and while this will run, it raises an exception from inside the Notifier.notify() that is supposed to be mocked, so it appears that it isn't being mocked at all.
What am I missing? Any help is much appreciated. 🙏
I believe it's because you're importing main at the start of the file. You need to do a dynamic import the same way as Notifier.mjs
// test/Main.test.mjs
import { jest } from '#jest/globals'
describe('fetch', () => {
test('Notifies on error', async () => {
const mockNotify = jest.fn();
jest.unstable_mockModule('../src/Notifier.mjs', () => ({
notify: mockNotify
}))
const notifierMock = await import('../src/Notifier.mjs');
const main = await import('../src/Main.mjs');
await main.fetch(null, null, null)
expect(mockNotify).toHaveBeenCalled()
})
})

Jest testing context / spy on mocked variables created outside of functions (class level) Postmark

I'm trying to do some testing in Jest but getting stuck with a mock/spy. I've managed to get the test working but only by changing my implementation (which I feel dirty about).
Here's the test:
import * as postmark from 'postmark';
jest.mock('postmark');
const mockGetServers = jest.fn();
const AccountClient = jest.fn(() => {
return {
getServers: mockGetServers
};
});
postmark.AccountClient = AccountClient;
import accountApi from './account-api';
describe('account-api', () => {
describe('listServers', () => {
it('calls postmark listServers', async () => {
await accountApi.listServers();
expect(mockGetServers).toHaveBeenCalledTimes(1);
});
});
});
Here's the working implementation:
import * as postmark from 'postmark';
const accountToken = 'some-token-number';
const listServers = async () => {
try {
const accountClient = postmark.AccountClient(accountToken);
const servers = await accountClient.getServers();
return servers;
} catch (e) {
console.log('ERROR', e);
}
};
export default {
listServers
}
Here's the original implementation:
import * as postmark from 'postmark';
const accountToken = 'some-token-number';
const accountClient = postmark.AccountClient(accountToken);
const listServers = async () => {
try {
const servers = await accountClient.getServers();
return servers;
} catch (e) {
console.log('ERROR', e);
}
};
export default {
listServers
}
The only change is where in the code the accountClient is created (either inside or outside of the listServers function). The original implementation would complete and jest would report the mock hadn't been called.
I'm stumped as to why this doesn't work to start with and guessing it's something to do with context of the mock. Am I missing something about the way jest works under the hood? As the implementation of accountApi will have more functions all using the same client it makes sense to create one for all functions rather than per function. Creating it per function doesn't sit right with me.
What is different about the way I have created the accountClient that means the mock can be spied on in the test? Is there a way I can mock (and spy on) the object that is created at class level not at function level?
Thanks
Am I missing something about the way jest works under the hood?
Two things to note:
ES6 import calls are hoisted to the top of the current scope
babel-jest hoists calls to jest.mock to the top of their code block (above everything including any ES6 import calls in the block)
What is different about the way I have created the accountClient that means the mock can be spied on in the test?
In both cases this runs first:
jest.mock('postmark');
...which will auto-mock the postmark module.
Then this runs:
import accountApi from './account-api';
In the original implementation this line runs:
const accountClient = postmark.AccountClient(accountToken);
...which captures the result of calling postmark.AccountClient and saves it in accountClient. The auto-mock of postmark will have stubbed AccountClient with a mock function that returns undefined, so accountClient will be set to undefined.
In both cases the test code now starts running which sets up the mock for postmark.AccountClient.
Then during the test this line runs:
await accountApi.listServers();
In the original implementation that call ends up running this:
const servers = await accountClient.getServers();
...which drops to the catch since accountClient is undefined, the error is logged, and the test continues until it fails on this line:
expect(mockGetServers).toHaveBeenCalledTimes(1);
...since mockGetServers was never called.
On the other hand, in the working implementation this runs:
const accountClient = postmark.AccountClient(accountToken);
const servers = await accountClient.getServers();
...and since postmark is mocked by this point it uses the mock and the test passes.
Is there a way I can mock (and spy on) the object that is created at class level not at function level?
Yes.
Because the original implementation captures the result of calling postmark.AccountClient as soon as it is imported, you just have to make sure your mock is set up before you import the original implementation.
One of the easiest ways to do that is to set up your mock with a module factory during the call to jest.mock since it gets hoisted and runs first.
Here is an updated test that works with the original implementation:
import * as postmark from 'postmark';
jest.mock('postmark', () => { // use a module factory
const mockGetServers = jest.fn();
const AccountClient = jest.fn(() => {
return {
getServers: mockGetServers // NOTE: this returns the same mockGetServers every time
};
});
return {
AccountClient
}
});
import accountApi from './account-api';
describe('account-api', () => {
describe('listServers', () => {
it('calls postmark listServers', async () => {
await accountApi.listServers();
const mockGetServers = postmark.AccountClient().getServers; // get mockGetServers
expect(mockGetServers).toHaveBeenCalledTimes(1); // Success!
});
});
});
I think you might want to look at proxyquire.
import * as postmark from 'postmark';
import * as proxyquire from 'proxyquire';
jest.mock('postmark');
const mockGetServers = jest.fn();
const AccountClient = jest.fn(() => {
return {
getServers: mockGetServers
};
});
postmark.AccountClient = AccountClient;
import accountApi from proxyquire('./account-api', postmark);
describe('account-api', () => {
describe('listServers', () => {
it('calls postmark listServers', async () => {
await accountApi.listServers();
expect(mockGetServers).toHaveBeenCalledTimes(1);
});
});
});
Note that I have not tested this implementation; tweaking may be required.

Categories