Jest testing conditional response - jestjs

I have data
const data = {
animal: 'cat',
isBlack: true
}
What I wish my component to render some text if the cat is black and not to render if the cat is not black. I have several tests
describe('About', () => {
const data = {
animal: 'cat',
isBlack: true
}
beforeEach(() => {
fetchMock.getOnce('/api/animals', {data});
});
afterEach(() => {
fetchMock.reset();
expect(fetchMock.done()).toBe(true);
});
it('should render with first text', () => {
expect(instance.getByText('Black Cats are awesome')).toBeVisible();
})
it('should render with second text', () => {
expect(instance.getByText('Black Cats are my favourite')).toBeVisible();
})
it('should render with second text', () => {
expect(instance.getByText('Black Cats rule')).toBeVisible();
})
it('should render no text', () => {
expect(instance.queryByText('Black Cats rule')).toBeNull();
})
)
So basically as soon as isBlack is false no text should be rendered. So, what I have now, I have fetchMock in every test, my desire is that I do run fetchMock before each and change the response for the false cases in the tests accordingly. Is there a way?

I cannot see yout code but assuming it is more less like this:
the component CatComponent
import React from "react";
import { fetchData } from "./fetchCatData";
const CatComponent = () => {
const [data, setData] = React.useState({});
React.useEffect(() => {
const fetchCatData = async () => {
const data = await fetchData();
setData(data);
};
fetchCatData();
}, []);
return (
<div>
{data && data.isBlack && (
<>
<p>Black Cats are awesome</p>
<p>Black Cats are my favourite</p>
<p>Black Cats rule</p>
<p>Black Cats rule</p>
</>
)}
</div>
);
};
export default CatComponent;
the fetch api: fetchCatData
export const fetchData = async () => {
return Promise.resolve({ animal: 'dog', isBlack: false })
}
We can have tests like this:
import { render, screen, waitFor } from "#testing-library/react";
import CatComponent from "../CatComponent";
const mockFetchData = jest.fn();
jest.mock("../fetchCatData", () => ({
fetchData: () => mockFetchData(),
}));
describe("should work tests", () => {
it("no text", async () => {
render(<CatComponent />);
await waitFor(() => {
expect(
screen.queryByText("Black Cats are awesome")
).not.toBeInTheDocument();
});
});
it('texts present', async () => {
mockFetchData.mockResolvedValue({ isBlack: true })
render(<CatComponent />);
await waitFor(() => {
expect(
screen.getByText("Black Cats are awesome")
).toBeInTheDocument();
});
expect(screen.getByText('Black Cats are my favourite')).toBeInTheDocument();
})
});

Related

How to reset global variable on custom hook

I need to reset global variable on custom hook when unit testing React component. I have read few tutorials and StackOverflow answers to this simple task, but without luck to implement it correctly.
The problem
userInfo is undefined in the first and second test but when runs the third test userInfo is defined then on useEffect doesn't change the value... So my question is how to reset userInfo for each test.
jest.resetModules // doesn't work
jest.isolateModules // doesn't work
My simplest possible setup for single test is as following:
My Environment
"jest": "^24.9.0",
My Hook
import {useState, useEffect} from "react";
// This variable is an object save user info
let userInfo = null;
export default (authService) => {
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (userInfo !== null || authService === null) {
return;
}
setLoading(true);
authService
?.getUser()
.then((response) => {
userInfo = {owners: [{...response, cost_center: response.costCenter || "N/A"}]};
})
.catch(() => {
setError({
title: "authService Error",
message: "Error getting user",
status: 500
});
})
.finally(() => setLoading(false));
}, [authService]);
return [userInfo, error, loading];
};
My Test
import {renderHook} from "#testing-library/react-hooks";
import * as sinon from "sinon";
import {getSpyOfUseOktaAuth} from "../../../__tests__/";
import {Info, InfoFromRequest, InfoWithNoCostCenter} from "../../../__tests__/";
describe("useGetUserInfo", () => {
let clock;
beforeEach(() => {
clock = sinon.useFakeTimers();
jest.useFakeTimers();
});
afterAll(() => {
clock.restore();
});
it("should set the error value after the getUserInfo function throws an error", async () => {
const useGetUserInfo = require("../index").default;
const errorMessage = {
title: "authService Error",
message: "Error getting user from",
status: 500
};
const getAuthMock = getSpyOfUseAuth({
Auth: {
signOut: jest.fn(),
getUser: jest.fn(async () => {
throw new Error("Auth Error");
})
},
authState: {}
});
const {result, rerender, waitForNextUpdate} = renderHook(() =>
useGetUserInfo(getAuthMock.results.Auth)
);
rerender();
await waitForNextUpdate();
expect(result.current[1]).toEqual(errorMessage);
getAuthMock.instance.mockRestore();
});
it("should return the user info from after run the getUserInfo function", async () => {
const useGetUserInfo = require("../index").default;
let authService = null;
const {result, rerender, waitForNextUpdate} = renderHook(() => useGetOktaUserInfo(authService));
const getAuthMock = getSpyOfUseAuth({
Auth: {
signOut: jest.fn(),
getUser: jest.fn(async () => Info)
},
authState: {}
});
authService = getAuthMock.results.Auth;
rerender();
await waitForNextUpdate();
expect(result.current[0]).toEqual(InfoFromRequest);
getAuthMock.instance.mockRestore();
});
it("should set cost_center as in data as N/A if costCenter is not defined in user info ", async () => {
const useGetUserInfo = require("../index").default;
const getAuthMock = getSpyOfUseAuth({
Auth: {
signOut: jest.fn(),
getUser: jest.fn(async () => InfoWithNoCostCenter)
},
authState: {}
});
const {result, rerender} = renderHook(() => useGetUserInfo(getAuthMock.results.Auth));
rerender();
expect(result.current[0].owners[0].cost_center).toEqual("N/A");
getAuthMock.instance.mockRestore();
});
});
I would say that either you export the 'userInfo' variable from the hook and you set it to null manually before each test, or you treat 'userInfo' as a state variable just like 'error' and 'loading'
If you go for the first option, you will need to export by reference Node Modules - exporting a variable versus exporting functions that reference it?
For the second option, it would be something like this
import {useState, useEffect} from "react";
export default (authService) => {
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const [userInfo, setUserInfo] = useState(null);
useEffect(() => {
if (userInfo !== null || authService === null) {
return;
}
setLoading(true);
authService
?.getUser()
.then((response) => {
setUserInfo({owners: [{...response, cost_center: response.costCenter || "N/A"}]});
})
.catch(() => {
setError({
title: "authService Error",
message: "Error getting user",
status: 500
});
})
.finally(() => setLoading(false));
}, [authService]);
return [userInfo, error, loading];
};
The solution I got is to separate each case into different files. As jest load each file in a different process, this will be enough

Jest - resetting value in module and reloading import

I'm writing a test in jest for a module which uses a constant from a different module.
I want to set a different value for it for every test case, but I don't seem to be able to do so.
The test file:
import { Request, Response } from 'express';
const activityConsumer = require('../../src/utils/activity.consumer');
const mockRequest = {
params: {
activityArn: 'activityArn'
}
} as Request;
const mockedJsonFunction = jest.fn();
const mockResponse: any = {
json: jest.fn(),
status: jest.fn().mockReturnValue({ json: mockedJsonFunction }),
} as Response;
let stopConsumerMock;
describe('consumer handler', () => {
beforeAll(() => {
stopConsumerMock = activityConsumer.stopConsumer = jest.fn().mockReturnValue(1);
});
beforeEach(() => {
jest.resetModules();
});
afterEach(() => {
stopConsumerMock.mockClear();
mockResponse.json.mockClear();
});
describe('stopConsumingHandler', () => {
it('Should return success true and not call stopConsumer when no consumer exists', () => {
activityConsumer.consumer = undefined;
const { stopConsumingHandler } = require ('../../src/handlers/consumer.handlers');
stopConsumingHandler(mockRequest, mockResponse);
expect(stopConsumerMock.mock.calls.length).toEqual(0);
expect(mockResponse.json.mock.calls.length).toEqual(1);
expect(mockResponse.json).toHaveBeenCalledWith({ success: true });
});
it('Should return success true and call stopConsumer when consumer exists', () => {
activityConsumer.consumer = true;
const { stopConsumingHandler } = require ('../../src/handlers/consumer.handlers');
stopConsumingHandler(mockRequest, mockResponse);
expect(stopConsumerMock.mock.calls.length).toEqual(1);
expect(mockResponse.json.mock.calls.length).toEqual(1);
expect(mockResponse.json).toHaveBeenCalledWith({ success: true });
});
});
});
I want to replace the value of activityConsumer.consumer and then reload the consumer.handlers module but the re-assignment and reload does not seem to have any effect.
Please advise on how can I write this test properly.
Try this way, using jest.mock to modify import value of activityConsumer
import { Request, Response } from 'express';
// const activityConsumer = require('../../src/utils/activity.consumer');
const mockRequest = {
params: {
activityArn: 'activityArn'
}
} as Request;
const mockedJsonFunction = jest.fn();
const mockResponse: any = {
json: jest.fn(),
status: jest.fn().mockReturnValue({ json: mockedJsonFunction }),
} as Response;
let stopConsumerMock;
describe('consumer handler', () => {
beforeAll(() => {
// stopConsumerMock = activityConsumer.stopConsumer = jest.fn().mockReturnValue(1);
stopConsumerMock = jest.fn().mockReturnValue(1);
});
beforeEach(() => {
jest.resetModules(); // important line
});
afterEach(() => {
stopConsumerMock.mockClear();
mockResponse.json.mockClear();
});
describe('stopConsumingHandler', () => {
it('Should return success true and not call stopConsumer when no consumer exists', () => {
// activityConsumer.consumer = undefined;
// mock by this way
jest.mock('../../src/utils/activity.consumer', () => ({
consumer: undefined,
stopConsumer: stopConsumerMock,
}));
const { stopConsumingHandler } = require('../../src/handlers/consumer.handlers');
stopConsumingHandler(mockRequest, mockResponse);
expect(stopConsumerMock.mock.calls.length).toEqual(0);
expect(mockResponse.json.mock.calls.length).toEqual(1);
expect(mockResponse.json).toHaveBeenCalledWith({ success: true });
});
it('Should return success true and call stopConsumer when consumer exists', () => {
// activityConsumer.consumer = true;
// mock by this way
jest.mock('../../src/utils/activity.consumer', () => ({
consumer: true, // mock value for consumer
stopConsumer: stopConsumerMock,
}));
const { stopConsumingHandler } = require('../../src/handlers/consumer.handlers');
stopConsumingHandler(mockRequest, mockResponse);
expect(stopConsumerMock.mock.calls.length).toEqual(1);
expect(mockResponse.json.mock.calls.length).toEqual(1);
expect(mockResponse.json).toHaveBeenCalledWith({ success: true });
});
});
});

Convert a class component into a function component

I want to convert this class component into a function component. I tried a lot and failed. Is there a solution to the problem?
componentDidMount(){
const id = this.props.match.params.id;
getById( parseInt(id) )
.then(product => {
this.setState({
product,
loading: false
});
})
}
The following snippet is equal componentDidMount in class component:
// ...
const [loading, setLoading] = React.useState(false);
const [product, setProduct] = React.useState(void 0);
React.useEffect(() => {
// Did mount, put your code here
setLoading(true)
// Fetch done
.then(product => {
setLoading(false)
setProduct(product)
})
}, []);

How to test objection.js with jest?

Model.knex(knex);
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(i18nextMiddleware);
I want to test method getUsers of users controller. It get data from usersModel getUsers method. I get date from MySQL by Objections ORM.
app.get(
'/',
checkFilter(['type']),
users.getUsers
);
According to instructions, I am changing the query method.
class MyQueryBuilder extends QueryBuilder {
query() {
return this.resolve({ test: 11111 });
}
}
class UsersModel extends Model {
static get QueryBuilder() {
return MyQueryBuilder;
}
}
jest.spyOn(Users, 'query')
.mockImplementation(UsersModel.query);
Describe test.
describe('get errors', () => {
beforeAll(done => {
i18next.on('initialized', () => {
done()
});
});
it('with filter', done => {
request(app)
.get('/')
.query({page: 0, perPage: 5, type: 'admin'})
.end((err, res) => {
if (err) return done(err);
expect(res.status).toBe(200);
expect(
Object.keys(res.body).sort()
).toEqual([
'items',
'itemsOnPage',
'currentPage',
'totalPage',
'totalItems'
].sort());
expect(res.body.items).toHaveLength(8);
expect(res.body.totalItems).toBe(usersMockDB.getUsers.length);
console.log(res.body);
done();
});
});
afterAll(done => {
knex.destroy();
done();
})
});
Method getUsers of users model.
const { Users } = require('../../db/models/Users');
const query = Users
.query()
.select(
'id',
'login',
'type',
'edit',
'email',
'phone',
'block'
)
.orderBy('id', 'DESC')
.page(page, perPage);
// filter
if (Object.keys(usersFilter).length) {
for (let field in usersFilter) {
if ( usersFilter.hasOwnProperty(field) ) {
query.where(field, 'like', `%${ usersFilter[field] }%`);
}
}
}
const { results, total } = await query;
return {
items: results,
itemsOnPage: Number(perPage),
currentPage: Number(page),
totalPage: Math.ceil(total/perPage),
totalItems: Number(total)
}
Should I override methods page and where ? As I understand it, they make new database queries.
This may not be desirable in every case, but I find that the easiest solution for unit tests that use objection models is to create one transaction per test. This does mean you'll need a database to run tests, but everything is rolled back between tests.
In jest.config.js, add this line
setupFilesAfterEnv: ['./jest.setup.js'],
in jest.setup.js:
import objection from 'objection';
import knex from './src/db/index.js'; // Change src/db/index.js to the path to the file where you export your knex instance
const {transaction, Model} = objection;
global.beforeAll(async () => {
global.knex = knex;
global.txn = null;
});
global.beforeEach(async () => {
global.txn = await transaction.start(knex);
Model.knex(global.txn);
});
global.afterEach(async () => {
await global.txn.rollback();
Model.knex(knex);
});
global.afterAll(async () => {
global.knex.destroy();
});
You can then use your models as expected in your code and unit tests
import {User} from './src/db/models/index.js';
it('creates and reads users', async () => {
const user = await User.query().insert({email: 'test#test.com'});
const users = await User.query();
expect(users).toHaveLength(1);
});
it('does not persist users between tests', async () => {
const users = await User.query();
expect(users).toHaveLength(1);
});

Unit test for customPollingHook which uses apollo useLazyQuery

So I have written a custom polling hook which uses useContext and useLazyQuery hooks. I want to write a unit test for this, which should cover its returned values state and side effect.
So far I have managed to do this much but I'm not so sure how to proceed ahead. Any tips?
export const useUploadActivityPolling = (
teId: TeIdType
): UploadActivityPollingResult => {
const { dispatch, uploadActivityId }: StoreContextType = useAppContext();
const [fetchActivityStatus, { error: UploadActivityError, data: UploadActivityData, stopPolling }] = useLazyQuery(
GET_UPLOAD_ACTIVITY,
{
pollInterval: 3000,
fetchPolicy: 'network-only',
variables: { teId, activityId: uploadActivityId },
}
);
useEffect(() => {
if (UploadActivityData) {
setUploadActivityId(
UploadActivityData.getUploadActivityStatus.activity_id,
dispatch
);
updateActivityStateAction(UploadActivityData.getExcelUploadActivityStatus.status, dispatch);
}
}, [UploadActivityData]);
return { fetchActivityStatus, stopPolling, UploadActivityError };
};
import React from 'react';
import { mount } from 'enzyme';
const TestCustomHook = ({ callback }) => {
callback();
return null;
};
export const testCustomHook = callback => {
mount(<TestCustomHook callback={callback} />);
};
describe('useUploadActivityPolling', () => {
let pollingResult;
const teId = 'some id';
beforeEach(() => {
testCustomHook(() => {
pollingResult = useUploadActivityPolling(teId);
});
});
test('should have an fetchActivityStatus function', () => {
expect(pollingResult.fetchActivityStatus).toBeInstanceOf(Function);
});
});

Resources