Check if functions have been called in unit test - javascript

Hi I'm trying to write some unit tests in Jest for a module I write, but kind of stuck currently and need some advice how to continue.
export const submitOrder = async (body, key) => {
const clientRepo = new ClientRepository(db)
const companyRepo = new CompanyRepository(db)
const company = await getCompanyByKey(
companyRepo,
key
);
const client = await createClient(
clientRepo,
body
);
await addClientToCompany(
companyRepo,
client.id,
company.id
);
.. More things
}
I can easily test each function(getCompanyByKey, createClient & addClientToCompany) by passing down a mocked repository.
But I would also like to test my "flow" of the submitOrder function, by checking if my repository functions have been called. But I would then need the instance of each repository, which I don't instantiate until my submitOrder function.
Something like this, which is similar how I unit test my functions.
jest.mock('../repositories/ClientRepository');
jest.mock('../repositories/CompanyRepository');
test('should be able to submit an order', async () => {
const apiKey = 'mocked-super-key';
const body = getMockData();
const result = await submitOrder(body, apiKey);
expect(result).toMatchSnapshot();
expect(CompanyRepository.findByKey).toHaveBeenCalled();
expect(ClientRepository.create).toHaveBeenCalled();
expect(CompanyRepository.addClient).toHaveBeenCalled();
});
Do you have any tips of how I can test if my repositories have been called?

The problem you describe is one of the motivating factors behind dependency injection.
As a single example: your submitOrder() code uses new to directly instantiate a client repository of the specific implementation ClientRepository. Instead, it could declare that it has a dependency - it needs an object that implements the interface of a client repository. It could then allow for such an object to be supplied by the surrounding environment (a "dependency injection container" in buzzword-ese). Then during testing you would create and provide ("inject") a mock implementation instead of the real implementation.
This has the added benefit that if you ever have to be able to select between multiple "real" implementations, you're already set up to do that too.
There are many ways to achieve this. It can be as simple as a design pattern, or for a more complete solution you could use a dependency injection framework.
If you absolutely cannot refactor your code for this practice, then JavaScript is dynamic enough that you can probably cobble together a way to intercept the invocation of new and thereby simulate dependency injection.

You can pass a mock implementation factory as a second parameter to jest.mock, as described in the docs.
You can use this to mock out the methods that you want to check to have been called.
Try this:
jest.mock('../repositories/CompanyRepository', () => {
findByKey: jest.fn(),
addClient: jest.jn()
});
const mockCreate = jest.fn();
jest.mock('../repositories/CompanyRepository', () => class {
create(...args) {
mockCreate(...args);
}
});
test('should be able to submit an order', async () => {
const apiKey = 'mocked-super-key';
const body = getMockData();
const result = await submitOrder(body, apiKey);
expect(result).toMatchSnapshot();
expect(CompanyRepository.findByKey).toHaveBeenCalled();
expect(ClientRepository.create).toHaveBeenCalled();
expect(CompanyRepository.addClient).toHaveBeenCalled();
});
Since CompanyRepository is created with “new”, we use a class definition in this case and pass in a mock function that is called when the “create” method is invoked.

Related

How to wrap code that uses transcations in a transaction and then rollback?

I'm setting up my integration testing rig. I'm using the beforeEach and afterEach hooks to wrap every single test in a transaction that rollsback so that the tests don't affect each other. A simplified example might be this:
const { repository } = require("library")
describe("Suite", function () {
beforeEach(async function () {
await knex.raw("BEGIN");
});
afterEach(async function () {
await knex.raw("ROLLBACK");
});
it("A test", async function () {
const user = await repository.createUser()
user.id.should.equal(1)
});
});
This worked fine because I configured knex to use a single DB connection for tests. Hence calling knex.raw("BEGIN"); created a global transaction.
Now however, the library's repository which I can't control started using transactions internally. I.e. createUser() begins and then commits the created user. This broke my tests as now my afterEach hook doesn't rollback the changes because they were already committed.
Is there a way in Postgres to rollback a transaction that have (already committed) nested transactions?
Or maybe a way to use knex to prevent the repository from starting transactions in the first place? It uses knex.transaction() to create them.
Thanks!
Judging by the looks of an example debug log, knex does in fact detect transaction nesting automatically and switches nested transactions from using irreversible commit/rollback to manageable savepoint s1/release s1/rollback to s1 the way I was guessing in my comment.
In this case, it should be enough for you to wrap your calls in a transaction, so that you "own" the top-level one. Knex should detect this and force the underlying transactions to use savepoints instead of commits, all of which you can then undo, rolling back the top-level transaction. If I read the doc right:
const { repository } = require("library")
describe("Suite", function () {
it("A test", async function () {
try {
await knex.transaction(async trx => {
const user = await repository.createUser();
user.id.should.equal(1);
trx.rollback();
})
} catch (error) {
console.error(error);
}
});
});
That's assuming none of the calls below issues a knex.raw("COMMIT") or somehow calls .commit() on the outer, top-level transaction.
As may be guessed from the tags the library in question is Strapi and I'm trying to write tests for the custom endpoints I implemented with it.
As noted by #zagarek, Postgres itself can't rollback already committed transactions. Knex does support nested transactions (using save-points) but you must explicitly refer to the parent transaction when creating a new one for it to get nested.
Many tried to achieve this setup. See the threads under e.g. here or here. It always boils down to somehow passing the test-wrapping transcation all the way down to your ORM/repository/code under test and instructing it to scope all queries under that transaction.
Unfortunately, Strapi doesn't provide any way to be given a transaction nor to create a global one. Now, cover your eyes and I'll tell you how I hacked around this.
I leverage one nice aspect of Knex: its Transaction object behaves (mostly) the same as a Knex instance. I mercilessly replace Strapi's reference of Knex instance with a Knex transaction and then rollback it in afterEach hook. To not make this too easy, Strapi extends its knex instance with a getSchemaName function. I therefore extend the transaction in disguise too and proxy to the original.
This does it: (Note that I'm using Mocha where this can be used to pass state between hooks and/or tests.)
const Strapi = require("#strapi/strapi");
before(async function () {
// "Load" Strapi to set the global `strapi` variable.
await Strapi().load();
// "Listen" to register API routes.
await strapi.listen();
// Put aside Strapi's knex instance for later use in beforeEach and afterEach hooks.
this.knex = strapi.db.connection;
});
after(async function () {
// Put back the original knex instance so that Strapi can destroy it properly.
strapi.db.connection = this.knex;
await strapi.destroy();
});
beforeEach(async function () {
// Replace Strapi's Knex instance with a transaction.
strapi.db.connection = Object.assign(await this.knex.transaction(), {
getSchemaName: this.knex.getSchemaName.bind(this.knex),
});
});
afterEach(async function () {
strapi.db.connection.rollback();
});
it("Health-check is available.", async function () {
// Any changes made within here will get rolled back once the test finishes.
await request(strapi.server.httpServer).get("/_health").expect(204);
});
Lastly, it's worth noting that some Knex maintainers persistently discourage using transcations to isolate tests so consider if chasing this hacky setup is a good idea.

Store data from server globally

Using jest and react-testing-libary.
I have data in my server, to get this data I need to log in, and then fetch it.
Is there a way to make this data available across all test files?
The way I usually do it is by using the beforeAll hook in the setupTests.js file. This way the code inside beforeAll hook will run before each test file is run and i will get fresh data each time.
get data in beforeAll async --> run test-file 1
get data in beforeAll async --> run test-file 2
...
...
I usually also cleanup everything with afterAll hook just to make sure my data is not getting mixed up in between test files being executed.
Sample code like so:
// setupTests.js file
beforeAll(async () => {
const response = await fetch("url-to-my-server");
const data = await response.json();
// store the data as a global variable by using global object
global.sampleData = data;
});
// cleanup any data or resources below:
afterAll(()=> global.sampleData='');
then inside my test files, I can use that data like this:
test("renders soemthing", () => {
const data = global.sampleData;
expect(data).toBe({whatever-i-expect-it-to-be});
});
More info in docs here
You should mock the front-end code that fetches data from your server, using Jest mock utilities. This allows you to control the server data in your test, so it is always the same, and to spy on HTTP calls made by your components.
If you have functions that send HTTP requests, you can mock these functions very easily and make them return values you need :
// in module.js:
// export const getData = () => { ... return Promise from HTTP call }
import * as MyHttpModule from 'module'
// in Jest test
const getDataMock = jest.spyOn(MyHttpModule, 'getData').mockResolvedValue(['mocked', 'data']);
This is just one way to do it, regarding your setup it might be different.
If you are using create-react-app you can add this code in setupTests.js so it will be executed before all tests.
You should never have real HTTP calls in your unit tests, it is also worth noting that Jest runs in a Node environment where fetch API is not available.
Jest Fetch Mock could also help you on this topic.

Stubbing function with jest

I understand that this is the basic function of jest and I should be able to figure it out with the docs and other online resources, but somehow I cannot, so I apologise in advance if this is trivial.
I'm trying to test a function in Javascript that performs a few operations using other modules, as well as localStorage, and I would like to stub out the other modules and the call to localStorage. The documentation that I found for Jest seemed far too simplistic for me to adapt to my use case, like declaring a mock and then calling it inside the test - this doesn't happen in my case, as the function I want to mock is being called internally by my function, I'm not passing it in as a dependency. Let me give some code to explain: file name is dataInLocalStorage.js
import serialize from './serialize'; // simple module that serialises data
import deserialize from './deserialize'; // simple module that deserialises data
import findObject from './findObject'; // find an object in the deserialised data
const addDataToLocalStorage = (data) => {
const dataStored = deserialize(localStorage.getItem('data')); // fetch data from localStorage
const isStored = !!findObject(dataStored, data); // check whether the data I want to store is already there
if (isStored) { return null; } // if data is already stored, skip
const serializedData = serialize(data); // serialise data to be stored
return localStorage.setItem('data', serializedData); // store serialised data in localStorage
};
export { addDataToLocalStorage };
The purpose os this module is just to store data in localStorage in a serialised way, but in an additive way, so that adding data doesn't remove previously stored data, and no duplicates are added either.
Anyway, my test file looks like this: file name is dataInLocalStorage.test.js
import { addDataToLocalStorage } from '../dataInLocalStorage';
describe('addDataToLocalStorage', () => {
const deserialize = jest.fn();
beforeAll(() => {
localStorage.removeItem('data');
});
const data = {
name: 'johnny'
};
addDataToLocalStorage(data);
it('adds the data to local storage', () => {
expect(deserialize).toHaveBeenCalled();
});
});
Here is the rather unsurprising error for this attempt.
expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called, but it was not called.
17 |
18 | it('adds the data to local storage', () => {
> 19 | expect(deserialize).toHaveBeenCalled();
| ^
20 | });
21 | });
On top of this I tried importing the deserialize function here in the test file and adding a jest.mock on that, which didn't work either.
Note that this isn't my code 100%, I have modified it for simplicity in order to make it easier to read for you, sorry if there are some slight mismatches, I tried my best to be as diligent as possible while converting it.
If you know what you're looking at, you'll see that this is obviously not working. Using other (more useful) expectations, the test was passing, but adding some console logs in the deserialize file showed that it's still running, when the idea is that I would like to mock it and provide my own return value.
Side note: I came from Ruby on Rails where mocking with RSpec is pretty simple, and I was hoping it would be just as simple with Jest. It likely is, but I can't wrap my head around it, as it doesn't seem possible to make a direct reference to the function/module I want to mock. In RSpec, doing allow(MySerializer).to receive(:call).and_return(...) would do the trick and I wouldn't have to worry about that module being called during the test.
When you set the value of deserialize to a jest mock, you are changing the variable value, not setting a reference that your code is using. To keep it a reference it needs to be a value in an object.
To import an object you can use import * as deserialize from "./deserialize";.
Then you can set the mock on the reference with deserialize.default = jest.fn().
https://codesandbox.io/s/88wlzp6q88
import { useIt } from "./use-default-export";
import * as myfunc from "./default-export-function";
test("use-default-export-function", () => {
expect(useIt()).toEqual("real");
});
test("use-default-export-function with mock", () => {
myfunc.default = jest.fn(() => "unreal");
expect(useIt()).toEqual("unreal");
});
in your test it'll be..
import { addDataToLocalStorage } from '../dataInLocalStorage';
import * as deserialize from './deserialize';
...
deserialize.default = jest.fn();
alternate TS compat version..
(which is actually cleaner all round..)
import { useIt } from "./use-default-export";
import myfunc from "./default-export-function";
jest.mock("./default-export-function", () => jest.fn());
test("use-default-export-function with mock", () => {
useIt();
expect(myfunc).toHaveBeenCalled();
});
return/resolve different values per test
(need to cast to jest.Mock to be able to use jest.fn() functions)
test("use-default-export-function with mock", () => {
const aFunc = myfunc as jest.Mock;
aFunc.mockResolvedValue("bar");
useIt();
expect(useIt()).resolves.toEqual("bar");
});
test("use-default-export-function with mock 2", () => {
const aFunc = myfunc as jest.Mock;
aFunc.mockReturnValue("foo");
useIt();
expect(useIt()).toEqual("foo");
});

How to mock an asynchronous function call in another class

I have the following (simplified) React component.
class SalesView extends Component<{}, State> {
state: State = {
salesData: null
};
componentDidMount() {
this.fetchSalesData();
}
render() {
if (this.state.salesData) {
return <SalesChart salesData={this.state.salesData} />;
} else {
return <p>Loading</p>;
}
}
async fetchSalesData() {
let data = await new SalesService().fetchSalesData();
this.setState({ salesData: data });
}
}
When mounting, I fetch data from an API, which I have abstracted away in a class called SalesService. This class I want to mock, and for the method fetchSalesData I want to specify the return data (in a promise).
This is more or less how I want my test case to look like:
predefine test data
import SalesView
mock SalesService
setup mockSalesService to return a promise that returns the predefined test data when resolved
create the component
await
check snapshot
Testing the looks of SalesChart is not part of this question, I hope to solve that using Enzyme. I have been trying dozens of things to mock this asynchronous call, but I cannot seem to get this mocked properly. I have found the following examples of Jest mocking online, but they do not seem to cover this basic usage.
Hackernoon: Does not use asychronous calls
Wehkamp tech blog: Does not use asynchronous calls
Agatha Krzywda: Does not use asynchronous calls
GitConnected: Does not use a class with a function to mock
Jest tutorial An Async Example: Does not use a class with a function to mock
Jest tutorial Testing Asynchronous Code: Does not use a class with a function to mock
SO question 43749845: I can't connect the mock to the real implementation in this way
42638889: Is using dependency injection, I am not
46718663: Is not showing how the actual mock Class is implemented
My questions are:
How should the mock class look like?
Where should I place this mock class?
How should I import this mock class?
How do I tell that this mock class replaces the real class?
How do set up the mock implementation of a specific function of the mock class?
How do I wait in the test case for the promise to be resolved?
One example that I have that does not work is given below. The test runner crashes with the error throw err; and the last line in the stack trace is at process._tickCallback (internal/process/next_tick.js:188:7)
# __tests__/SalesView-test.js
import React from 'react';
import SalesView from '../SalesView';
jest.mock('../SalesService');
const salesServiceMock = require('../SalesService').default;
const weekTestData = [];
test('SalesView shows chart after SalesService returns data', async () => {
salesServiceMock.fetchSalesData.mockImplementation(() => {
console.log('Mock is called');
return new Promise((resolve) => {
process.nextTick(() => resolve(weekTestData));
});
});
const wrapper = await shallow(<SalesView/>);
expect(wrapper).toMatchSnapshot();
});
Sometimes, when a test is hard to write, it is trying to tell us that we have a design problem.
I think a small refactor could make things a lot easier - make SalesService a collaborator instead of an internal.
By that I mean, instead of calling new SalesService() inside your component, accept the sales service as a prop by the calling code. If you do that, then the calling code can also be your test, in which case all you need to do is mock the SalesService itself, and return whatever you want (using sinon or any other mocking library, or even just creating a hand rolled stub).
You could potentially abstract the new keyword away using a SalesService.create() method, then use jest.spyOn(object, methodName) to mock the implementation.
import SalesService from '../SalesService ';
test('SalesView shows chart after SalesService returns data', async () => {
const mockSalesService = {
fetchSalesData: jest.fn(() => {
return new Promise((resolve) => {
process.nextTick(() => resolve(weekTestData));
});
})
};
const spy = jest.spyOn(SalesService, 'create').mockImplementation(() => mockSalesService);
const wrapper = await shallow(<SalesView />);
expect(wrapper).toMatchSnapshot();
expect(spy).toHaveBeenCalled();
expect(mockSalesService.fetchSalesData).toHaveBeenCalled();
spy.mockReset();
spy.mockRestore();
});
One "ugly" way I've used in the past is to do a sort of poor-man's dependency injection.
It's based on the fact that you might not really want to go about instantiating SalesService every time you need it, but rather you want to hold a single instance per application, which everybody uses. In my case, SalesService required some initial configuration which I didn't want to repeat every time.[1]
So what I did was have a services.ts file which looks like this:
/// In services.ts
let salesService: SalesService|null = null;
export function setSalesService(s: SalesService) {
salesService = s;
}
export function getSalesService() {
if(salesService == null) throw new Error('Bad stuff');
return salesService;
}
Then, in my application's index.tsx or some similar place I'd have:
/// In index.tsx
// initialize stuff
const salesService = new SalesService(/* initialization parameters */)
services.setSalesService(salesService);
// other initialization, including calls to React.render etc.
In the components you can then just use getSalesService to get a reference to the one SalesService instance per application.
When it comes time to test, you just need to do some setup in your mocha (or whatever) before or beforeEach handlers to call setSalesService with a mock object.
Now, ideally, you'd want to pass in SalesService as a prop to your component, because it is an input to it, and by using getSalesService you're hiding this dependency and possibly causing you grief down the road. But if you need it in a very nested component, or if you're using a router or somesuch, it's becomes quite unwieldy to pass it as a prop.
You might also get away with using something like context, to keep everything inside React as it were.
The "ideal" solution for this would be something like dependency injection, but that's not an option with React AFAIK.
[1] It can also help in providing a single point for serializing remote-service calls, which might be needed at some point.

Covering Anonymous Function Arguments in Unit Tests

I have multiple situations in code where we pass a function as an argument when creating an object (like so in onPress):
<Touchable
onPress={() => Linking.openURL(formatUrl(url))}
noContainer={true}>
{children}
</Touchable>
When it comes down to it, this is a snippet in a larger component getting rendered. As it's not actually a method of the component, I'm not especially interested in testing it here - it will get tested as part of another component if needed.
However, the code coverage report comes back with an indication that the function is uncovered.
Is there a way to satisfy this coverage - either by testing this function, or ignoring all functions passed in such a way (anonymously, as arguments)?
Assuming you are using Jest, you can set your Jest mock to call the argument that it receives, so:
const componentProps = {
onPress: jest.fn(func => func());
}
So the mocked onPress function, in this case, will receive your anonymous function and call it. Coverage will show that the anonymous function was called.
Hope it helps :)
First, I'd like to advise against exempting lines from coverage, especially if you're being paid to write this code and unit test it.
One thing I've found that works is to write a void function that calls your function with your desired arguments.
if you have:
onClick={ () => function(argument) }
and Jest isn't covering it, try:
const functionWithArgs = () => function(argument)
onClick={functionWithArgs}
I use this a lot for modals:
const [isModalOpen, setModalOpen] = React.useState(false)
const toggleModal = () => setModalOpen(!isModalOpen)
onClick={toggleModal}
But the same principle can be applied to a much more complex function. And it works!

Categories

Resources