Im starting with react-testing-library, and Im trying to test API calls. I have two sets, one for success request and another for error request.
import React from "react";
import { render, waitForElementToBeRemoved } from "#testing-library/react";
import user from "#testing-library/user-event";
import App from "./App";
import { getUser } from "./serviceGithub";
jest.mock("./serviceGithub");
//Mock data for success and error, Im using the github api
const dataSuccess = {
id: "2231231",
name: "enzouu",
};
const dataError = {
message: "not found",
};
const renderInit = () => {
const utils = render(<App />);
const inputUser = utils.getByPlaceholderText("ingrese usuario", {
exact: false,
});
const buttonSearch = utils.getByRole("button", { name: /buscar/i });
return { utils, buttonSearch, inputUser };
};
test("should success request to api", async () => {
getUser.mockResolvedValue([dataSuccess]);
const { utils, buttonSearch, inputUser } = renderInit();
expect(utils.getByText(/esperando/i)).toBeInTheDocument();
expect(buttonSearch).toBeDisabled();
user.type(inputUser, "enzzoperez");
expect(buttonSearch).toBeEnabled();
user.click(buttonSearch);
await waitForElementToBeRemoved(() =>
utils.getByText("cargando", { exact: false })
);
expect(getUser).toHaveBeenCalledWith("enzzoperez");
expect(getUser).toHaveBeenCalledTimes(1);
expect(utils.getByText("enzouu", { exact: false })).toBeInTheDocument();
});
test("should error request to api", async () => {
getUser.mockResolvedValue(dataError)
const { utils, buttonSearch, inputUser } = renderInit();
expect(buttonSearch).toBeDisabled();
user.type(inputUser, "i4334jnrkni43");
expect(buttonSearch).toBeEnabled();
user.click(buttonSearch)
await waitForElementToBeRemoved(()=>utils.getByText(/cargando/i))
expect(getUser).toHaveBeenCalledWith('i4334jnrkni43')
expect(getUser).toHaveBeenCalledTimes(1)
});
The problem here is that in the second test the last line expect(getUser).toHaveBeenCalledTimes(1) get error because getUseris calling 2 times, but if I comment the first test, the second pass..
So, how should I do to test this case? Its ok the way that Im doing the tests?
Thanks!
You can use jest.mockClear() with beforeEach() or afterEach()
For clean-up purpose, afterEach() would be more appropriate.
mockClear resets all the information stored in the mockFn.mock.calls which means that for every test, you can expect getUser being called, started from zero times.
afterEach(() => {
jest.clearAllMocks()
})
Furthermore, use screen from #testing-library/react instead of returned value of render when using queries. Also, mockResolvedValueOnce would be better in this case.
Related
Does someone knows how can I test this function in Jest? I don't have any ideas at this moment, maybe I need to mock Cookies ?
import Cookies from "js-cookie";
import { v4 as uuidv4 } from "uuid";
const setUserCookie = () => {
if (!Cookies.get("UserToken")) {
Cookies.set("UserToken", uuidv4(), { expires: 10 });
}
};
export default setUserCookie;
I tried this for now, but I don't know if this is correct, I don't think it tests the functionality of my function:
import Cookies from 'js-cookie';
import setCookie from './setCookie';
describe("setCookie", () => {
it("should set cookie", () => {
const mockSet = jest.fn();
Cookies.set = mockSet;
Cookies.set('testCookie', 'testValue');
setCookie()
expect(mockSet).toBeCalled();
});
});
Best way to test this is to utilize the actual logic, so I would change your test to the following:
it("should set cookie", () => {
// execute actual logic
setCookie();
// retrieve the result
const resultCookie = Cookies.get();
// expects here
expect(resultCookie["UserToken"]).toBeTruthy();
// expects for other values here...
});
To note, uuidv4() will generate a new value for every new test run, meaning that you cannot expect the same value for the "UserToken" property. Instead, you can use the following approach to tackle this problem:
First set up a spy for it:
import { v4 as uuidv4 } from "uuid";
jest.mock('uuid');
Then add its mock implementation with the expected result into the unit test:
const expectedUUIDV4 = 'testId';
uuidv4.mockImplementation(() => expectedUUIDV4);
// then expecting that in the result
expect(resultCookie["UserToken"]).toEqual(expectedUUIDV4);
I am visiting an old project and ran the tests. However, testing my Redux-Saga is not passing its error handling tests.
All The other tests are passing without problem so I know the functions are being called correctly. Below is the test file
import { takeLatest, call, put } from 'redux-saga/effects'
import { firestore, convertCollectionsSnapshotToMap } from '../../firebase/firebase.utils'
import { fetchCollectionsSuccess, fetchCollectionsFailure } from './shop.actions'
import ShopActionTypes from './shop.types'
import { fetchCollectionsAsync, fetchCollectionsStart } from './shop.sagas'
describe('fetch collections start saga', () => {
it('should trigger on FETCH_COLLECTIONS_START', () => {
const generator = fetchCollectionsStart()
expect(generator.next().value).toEqual(
takeLatest(ShopActionTypes.FETCH_COLLECTIONS_START, fetchCollectionsAsync)
)
})
})
describe('fetch collections async saga', () => {
const generator = fetchCollectionsAsync()
it('should call firestore collection ', () => {
const getCollection = jest.spyOn(firestore, 'collection')
generator.next()
expect(getCollection).toHaveBeenCalled()
})
it('should call convertCollectionsSnapshot saga ', () => {
const mockSnapshot = {}
expect(generator.next(mockSnapshot).value).toEqual(
call(convertCollectionsSnapshotToMap, mockSnapshot)
)
})
it('should fire fetchCollectionsSuccess if collectionsMap is succesful', () => {
const mockCollectionsMap = {
hats: { id: 1 }
}
expect(generator.next(mockCollectionsMap).value).toEqual(
put(fetchCollectionsSuccess(mockCollectionsMap))
)
})
// THIS IS THE FAILING CODE
it('should fire fetchCollectionsFailure if get collection fails at any point', () => {
const newGenerator = fetchCollectionsAsync()
newGenerator.next()
expect(newGenerator.throw({ message: 'error' }).value).toEqual(
put(fetchCollectionsFailure('error'))
)
})
})
Below is my shop Saga.js file
import { takeLatest, call, put, all } from 'redux-saga/effects'
import { firestore, convertCollectionsSnapshotToMap } from '../../firebase/firebase.utils'
import { fetchCollectionsSuccess, fetchCollectionsFailure } from './shop.actions'
import ShopActionTypes from './shop.types'
export function* fetchCollectionsAsync() {
try {
const collectionRef = firestore.collection('collections')
const snapshot = yield collectionRef.get()
const collectionsMap = yield call(convertCollectionsSnapshotToMap, snapshot)
yield put(fetchCollectionsSuccess(collectionsMap))
} catch (error) {
yield put(fetchCollectionsFailure(error.message))
}
}
export function* fetchCollectionsStart() {
yield takeLatest(ShopActionTypes.FETCH_COLLECTIONS_START, fetchCollectionsAsync)
}
export function* shopSagas() {
yield all([call(fetchCollectionsStart)])
}
I have tried mocking the error and passing it into my tests also but I am getting the same output in my tests below, no matter how I refactor the code.
PASS src/pages/checkout/checkout.test.js
FAIL src/redux/shop/shop.sagas.test.js
● fetch collections async saga › should fire fetchCollectionsFailure if get collection fails at any point
error
Test Suites: 1 failed, 1 passed, 2 total
Tests: 1 failed, 5 passed, 6 total
Snapshots: 1 passed, 1 total
Time: 3.076 s
Ran all test suites related to changed files
If anyone has any advice on where to look and how to fix this, it would be greatly appreciated. Thanks in advance
I have written the following code as a workaround. Tests are now passing and the fetchCollectionsFailure is being called. It's not the prettiest code but it's a workaround and fulfills the criteria for now.
it('should fire fetchCollectionsFailure if get collection fails at any point', () => {
const newGenerator = fetchCollectionsAsync()
const error = {
'##redux-saga/IO': true,
combinator: false,
payload: {
action: {
payload: "Cannot read property 'get' of undefined",
type: 'FETCH_COLLECTIONS_FAILURE'
},
channel: undefined
},
type: 'PUT'
}
expect(newGenerator.next().value).toMatchObject(error)
})
I have a jest test that is calling the real function and it compares the result returned with an expected result. The service function called uses uuid. I have all kind of errors while trying to mock uuid and can't seem to succeed.
My code is:
import uuid from 'uuid';
import tinyRuleset from './tiny_ruleset.json';
import { Store } from '../store';
describe('TuningStore test ', () => {
let store;
let db;
beforeEach(async () => {
db = levelup(encode(memdown(), { valueEncoding: 'json' }));
store= new Store(db);
});
test('createObject()', async () => {
jest.spyOn(uuid, 'v4').mockReturnValue('abc22');
const obj = await store.createObject();
expect(obj ).toEqual({
a: expect.any(string),
b: 'tiny_ruleset',
v: expect.any(Function)
});
});
})
I tried several ways, but none of them worked. My current error is: uuid is not a function. Also tried this:
const uuidv4Spy = jest.spyOn(store.$uuid, 'v4').mockReturnValueOnce('fake uuid');
Basically uuid is used inside the store.createObject() function.
Thank you!
As explained here Mock uuid
const uuidMock = jest.fn().mockImplementation(() => {
return 'my-none-unique-uuid';
});
jest.mock('uuid', () => {
return uuidMock;
});
you need to apply the mock in the test file before you are importing your real file.
I refer to the following tutorial regarding data fetching on ReactJS.org, because I wanted to use it as a template for my own test.
I use npm test, which calls react-scripts test, and, as far as i know, uses jasmine.
I created the user.js and user.test.js as described in the tutorial.
// user.test.js
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import User from "./user";
let container = null;
beforeEach(() => {
// setup a DOM element as a render target
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
// cleanup on exiting
unmountComponentAtNode(container);
container.remove();
container = null;
});
it("renders user data", async () => {
const fakeUser = {
name: "Joni Baez",
age: "32",
address: "123, Charming Avenue"
};
jest.spyOn(global, "fetch").mockImplementation(() =>
Promise.resolve({
json: () => Promise.resolve(fakeUser)
})
);
// Use the asynchronous version of act to apply resolved promises
await act(async () => {
render(<User id="123" />, container);
});
expect(container.querySelector("summary").textContent).toBe(fakeUser.name);
expect(container.querySelector("strong").textContent).toBe(fakeUser.age);
expect(container.textContent).toContain(fakeUser.address);
// remove the mock to ensure tests are completely isolated
global.fetch.mockRestore();
});
When I run the test I get Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.
setTimeout.Error
For some reason, it does not seem to resolve the promise.
What am I doing wrong? I can't believe this tutorial is bugged! Any help is appreciated!
It's working now. You strictly need react-dom >= 16.9.0
I am using jest and trying to test an asynchronous login request. I am able to check that the call has resolved and is successful. I would also like to test the case that the call wasn't successful.
I have been following the docs from here.
I understand I am not doing the reject correctly, but if I move the jest.mock('.utils/api', () => {... into the test block it doesn't work, it needs to be outside. Can anyone advise the correct way to do this?
See my code below:
import React from 'react';
import { render, fireEvent } from 'react-testing-library';
import Login from './index';
import { login as mockLogin } from './api';
let mockData = {
token: '12345'
};
let errorData = {
message: 'Your username/password is incorrect'
};
jest.mock('.utils/api', () => {
return {
jsonRequest: jest.fn(() => new Promise((resolve, reject) => {
resolve(mockData,);
// I am not doing this correctly.
reject(errorData);
})),
};
});
describe('<Login />', () => {
it('returns a sessionId if successful and error if not', () => {
const { getByLabelText, getByText } = render(<Login />);
const loginButton = getByText(/login/i);
fireEvent.click(loginButton);
expect(mockLogin).toBeCalledTimes(1);
expect(mockLogin).toHaveBeenCalledWith('/login', {
data: {
password: 'test',
username: 'test',
},
method: 'POST',
});
expect(mockLogin()).resolves.toBe(mockData);
expect(mockLogin()).rejects(mockData);
});
});
What you need to test here is the behavour of your component when the API rejects the request for some reason.
Assume this scenario:
Lets say the reason for rejection is that the "Entered password is not correct".
Then you need to make sure that the Login component will show an error message to the DOM where the user can see it and re-enter his password
To test this you need to make a check inside the mocked API, something like:
jsonRequest: jest.fn((formDataSubmittedFromComponent) => new Promise((resolve, reject) => {
// Notice that the mocked function will recieve the same arguments that the real function recieves from the Login component
// so you can check on these arguments here
if (formDataSubmittedFromComponent.password !== mockData.password) {
reject('Entered password is not correct') // << this is an error that your component will get and should handle it
}
resolve();
})),
After that you should test how did your component handle the reject
For example, you could test whether or not it displayed an error message to the DOM:
const errorMessageNode = getByTestId('error-message');
expect(errorMessageNode).toBeTruthy()
Edit: before you fire the login event you should make sure that the form is populated with the mocked data