How can i create fake functions to test the components? - javascript

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.

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 testing function call inside another function

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();
});

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.

How to mock const method in jest?

I unit test code in typescript, use jest. Please teach me how to mock getData to return the expected value. My code as below:
// File util.ts
export const getData = async () => {
// Todo something
return data;
}
// File execution.ts import { getData } from './util';
function execute()
{
// todo something
const data = await getData();
// todo something
}
The problem is that your function returns a promise. Depends on how you use it there are several ways to mock it.
The simplest way would be to mock it directly, but then it will always return the same value:
// note, the path is relative to your test file
jest.mock('./util', () => ({ getData: () => 'someValue' }));
If you want to test both the resolved and the rejected case you need to mock getData so it will return a spy where you later on can change the implementation use mockImplementation. You also need to use async/await to make the test work, have a look at the docs about asynchronous testing:
import { getData } from './util';
jest.mock('./util', () => ({ getData: ()=> jest.fn() }));
it('success case', async () => {
const result = Promise.resolve('someValue');
getData.mockImplementation(() => result);
// call your function to test
await result; // you need to use await to make jest aware of the promise
});
it('error case', async () => {
const result = Promise.reject(new Error('someError'));
getData.mockImplementation(() => result);
// call your function to test
await expect(result).rejects.toThrow('someError');
});
Try the following in your test file.
Import the function from the module.
import { getData } from './util';
Then mock the module with the function and its return value after all the import statements
jest.mock('./util', () => ({ getData: jest.fn() }))
getData.mockReturnValue("abc");
Then use it in your tests.
Because mocking expression functions can be a real pain to get right, I'm posting a full example below.
Scenario
Let's say we want to test some code that performs some REST call, but we don't want the actual REST call to be made:
// doWithApi.ts
export const doSomethingWithRest = () => {
post("some-url", 123);
}
Where the post is a function expression in a separate file:
// apiHelpers.ts
export const post = (url: string, num: number) => {
throw Error("I'm a REST call that should not run during unit tests!");
}
Setup
Since the post function is used directly (and not passed in as a parameter), we must create a mock file that Jest can use during tests as a replacement for the real post function:
// __mocks__/apiHelpers.ts
export const post = jest.fn();
Spy and Test
Now, finally inside the actual test, we may do the following:
// mockAndSpyInternals.test.ts
import {doSomethingWithRest} from "./doWithApi";
afterEach(jest.clearAllMocks); // Resets the spy between tests
jest.mock("./apiHelpers"); // Replaces runtime functions inside 'apiHelpers' with those found inside __mocks__. Path is relative to current file. Note that we reference the file we want to replace, not the mock we replace it with.
test("When doSomethingWithRest is called, a REST call is performed.", () => {
// If we want to spy on the post method to perform assertions we must add the following lines.
// If no spy is wanted, these lines can be omitted.
const apiHelpers = require("./apiHelpers");
const postSpy = jest.spyOn(apiHelpers, "post");
// Alter the spy if desired (e.g by mocking a resolved promise)
// postSpy.mockImplementation(() => Promise.resolve({..some object}))
doSomethingWithRest();
expect(postSpy).toBeCalledTimes(1)
expect(postSpy).toHaveBeenCalledWith("some-url", 123);
});
Examples are made using Jest 24.9.0 and Typescript 3.7.4

Cannot stub functions inside a module when using sinon and babel-plugin-rewire

In a mocha/chai setup, I'm trying to use babel-plugin-rewire in conjunction with sinon for testing and stubbing functions within the same module. These are the example files below:
Firstly, an index.js and test file that uses both sinon and babel-plugin-rewire. Rewiring works, but for some reason, my stubs don't work. The function it is applied to is never stubbed and only the original value is returned:
// index.js
function foo() {
return "foo";
}
export function bar() {
return foo();
}
export function jar() {
return "jar";
}
//index.test.js
import chai from "chai";
import sinon from "sinon";
import * as index from "./index";
const expect = chai.expect;
const sandbox = sinon.sandbox.create();
describe("babel-plugin-rewire", () => {
it("should be able to rewire", () => {
index.default.__set__("foo", () => {
return "rewired"; // successfullly rewires
});
expect(index.bar()).to.equal("rewired"); // works fine
index.default.__ResetDependency__("foo");
expect(index.bar()).to.equal("bar"); // works fine
});
});
describe("sinon", () => {
afterEach(() => {
sandbox.restore();
});
it("should call the original jar", () => {
expect(index.jar()).to.equal("jar"); // works fine
});
it("should call the stubbed jar", () => {
sandbox.stub(index, "jar").returns("stub");
expect(index.jar()).to.equal("stub"); // fails
});
});
And here is two example files solely using sinon stubs. The same thing happens:
// stub.js
export function stub() {
return "stub me";
}
// stub.test.js
import * as stub from "./stub";
import sinon from "sinon";
import chai from "chai";
const expect = chai.expect;
const sandbox = sinon.createSandbox();
const text = "I have been stubbed";
describe("sinon stubs", () => {
afterEach(() => {
sandbox.restore();
});
it("should stub", () => {
sandbox.stub(stub, "stub").returns(text);
expect(stub.stub()).to.equal(text); // fails
});
});
And this here is the babelrc that is being used for mocha
{
"presets": [
"#babel/preset-env"
],
"plugins": [
"rewire"
]
}
If I remove rewire from the plugins, the problem goes away. Though obviously this means I can't use rewire, which as I mentioned earlier I need in order to stub functions within the same dependency. Is this a bug with the module or am I missing something here?
I had the same problem with one of my tests. There was an action that after signing out the user, triggered a page redirect. The redirect itself was implemented in separate file.
import * as utils from '../utils.js';
import sinon from 'sinon';
it('will dispatch an action and redirect the user when singing out', () => {
const redirectStub = sinon.stub(utils, 'redirect').returns(true);
// dispatch and assertion of action omitted
expect(redirectStub.calledOnce).toBeTruthy();
redirectStub.restore();
});
Then, for various reasons, I had to add babel-rewire-plugin to my tests. This, broke that specific test above. The calledOnce or any other sinon methods were always returning false.
Unfortunately, I wasn't able to make the spying/stubbing work anymore. I had to rewrite my test like this:
import { __RewireAPI__ } from '../utils.js';
import sinon from 'sinon';
it('will dispatch an action and redirect the user when singing out', () => {
__RewireAPI__.__Rewire__('redirect', () => true);
// dispatch and assertion of action omitted
__RewireAPI__.ResetDependencty('redirect');
});
As you can see, there are no spies or stubs assertions anymore. I rewired the redirect method and then reset it.
There is a babel-plugin-rewire-exports library however, that seems to allow you to both rewire and spy/stub. I haven't tried it myself, but it can be an option if you don't want to rewrite your tests. Here is the link.

Categories