React testing mock implementation one, work with mocked funcitons - reactjs

I am implementing a mock function for firebase authentication, I have mocked the firebase module like this:
jest.mock("../../lib/api/firebase", () => {
return {
auth: {
createUserWithEmailAndPassword: jest.fn(() => {
return {
user: {
uid: "fakeuid",
},
};
}),
signOut: jest.fn(),
},
firestore: {
collection: jest.fn(() => ({
doc: jest.fn(() => ({
collection: jest.fn(() => ({
add: jest.fn(),
})),
set: jest.fn(),
})),
})),
},
};
});
right now createUserWithEmailAndPassword returns an user uid to fake the firebase response on signup. I use it in my test like this:
.
.
.
await wait(() => {
expect(auth.createUserWithEmailAndPassword).toHaveBeenCalled();
expect(history.location.pathname).toBe("/dashboard");
expect(getByText("You succesfully signed up!")).toBeInTheDocument();
});
it works perfectly fine, but how if I want the returned value to be something different for one test?
I saw there is mockImplementationOnce that seems to be the right path but I am struggling implementing it, any help?
Thanks,
F.

You can use mockReturnValueOnce like this:
auth.createUserWithEmailAndPassword
.mockReturnValueOnce(/* your desired return value here */)
Just make sure you are mocking the return value before calling auth.createUserWithEmailAndPassword in your test.
For example:
auth.createUserWithEmailAndPassword
.mockReturnValueOnce({user: { uid: 'mocked uid'}})
auth.createUserWithEmailAndPassword()
// Insert your expect statement(s) here
...

Related

getting issue in jest unit testing for login

i have created login page,which works fine, when i created unit test for that module with jest, i am getting issue, it doesn't return promiss when login api calls, here is my code for react and jest respectively, i am not getting console for console.log("after login"); can anyone please check my code and help to resolve this issue ?
validateAll(formData, rules, message).then(async () => {
dispatch(setLoading(true))
console.log("before login");
const login = await authApi.login(app, email, password)
console.log("after login");
if (login && login.error && login.error !== null) {
dispatch(setLoading(false))
ToastAlert({ msg: login.error.error || login.error.message, msgType: 'error' });
dispatch(setToken(''))
} else {
console.log("done");
dispatch(setToken(login.data._accessToken))
setSuccess(true)
dispatch(setLoading(false))
//router.replace("/");
ToastAlert({ msg: 'You have successfully logged in', msgType: 'success' });
}
}
auth
import { resolve } from "./resolve";
import * as Realm from "realm-web";
function auth() {
const register = async (app: any, data: object) => {
return await resolve(
app.emailPasswordAuth.registerUser(data)
.then((response: any) => response)
)
}
const login = async (app: any, email: string, password: string) => {
const credentials = await Realm.Credentials.emailPassword(email, password);
return await resolve(
app.logIn(credentials)
.then((response: any) => response)
)
}
const confirmUser = async (app: any, data: object) => {
return await resolve(
app.emailPasswordAuth.confirmUser(data)
.then((response: any) => response)
)
}
const logout = async (app: any) => {
return await resolve(
app.currentUser.logOut()
.then((response: any) => response)
)
}
return {
register,
login,
confirmUser,
logout
}
}
const authApi = auth();
export default authApi;
unit test
test('login with correct username and password', async () => {
const initialState = {
email: '',
password: '',
error: {
email: "",
password: ""
},
};
const mockStore = configureStore();
let store;
store = mockStore(initialState);
const { getByText,getByLabelText } = render(
<Provider store={store}>
<Authenticate />
</Provider>
);
// fill out the form
fireEvent.change(screen.getByLabelText('Email'), {
target: {value: '***#gmail.com'},
})
fireEvent.change(screen.getByLabelText(/Parola/i), {
target: {value: '****#123'},
})
const loginAwait = screen.getByText(/Authentificare/i);
await fireEvent.click(loginAwait)
// wait for the error message
const alert = await screen.findByRole('alert')
expect(alert).toHaveTextContent(/You have successfully logged in/i)
})
As mentioned in the comments you can't make real requests in jest. You have 2 big options to solve your issue:
Option 1.1: As was mentioned in the comment (and shown in the link) you can mock your request library (in your case is not axios, is Realm.Credentials.emailPassword, but you probably also have to mock the app.logIn part) :
Just replace Realm, for example, you should add something like this to your unit test:
...
const Realm = require("realm-web");
jest.mock("realm-web")
...
test('login with correct username and password', async () => {
Realm.Credentials.emailPassword.mockResolvedValue({token: "test-token"})
})
WARNING: As mentioned above this by itself most likely won't fix your problem since you have to also mock app.logIn (however assuming app.logIn is just calling under the hood Realm.App.logIn you might be able to mock that too by adding:
Realm.App.logIn.mockResolvedValue({user: {...})
(Realm.App might need to be Realm.App())
If Realm.Credentials.emailPassword throws an error you might need to define them first when defining jest.mock("realm-web"). So something like :
jest.mock("realm-web", () => ({
App: (config) => {
return {
logIn: jest.fn()
}
},
Credentials: {
emailPassword: jest.fn()
}
}))
or you can just mock the library at the beginning using something like:
jest.mock("realm-web", () => ({
App: (config) => {
return {
logIn: (token:string) => ({user: {...}})
}
},
Credentials: {
emailPassword: (email: string, password:string) => ({token: "test-token"})
}
}))
)
If this is not the case you need to figure how to mock that as well (is kind of hard to properly fix your issue without a working example). But assuming you are doing something like app = new Realm.App(...) you might want to check how to mock new Function() with Jest here. If you get to this, you will most likely need a hybrid solution (to mock both new Realm.App and Realm.Credentials.emailPassword)
You could also try to mock the entire module at once, at the beginning of the test file using something like:
jest.mock("realm-web", () => ({
App: (config) => {
return {
logIn: (token:string) => ({user: {...}})
}
},
Credentials: {
emailPassword: (email: string, password:string) => ({token: "test-token"})
}
}))
OBS: adjusments might be required. This is just an example.
Also please be aware that this will create a mock for all the tests following the execution of this code.
Option 1.2:
You could also use a similar strategy to mock your authApi (you have some examples on how to mock a default export here). Just make sure you mock the the login function from it (and if there is any other function from the api -> like confirmUser used on the same test mock that as well). This option would be easier to implement but it's really up to what you want your test to cover. Also a hybrid might be mocking the app part together with Realm.Credentials.emailPassword.
Option 2: You might find an existing solution. Here are some interesting links:
github Realm mock
react native example from mogoDB page -> this won't work copy-pasted, but might serve as inspiration.
Another maybe somehow related question would be How to fake Realm Results for tests. It is not answering your question but might also help a little.

How can i test localstorage properly?

how can i test localstorage? i wanna create a test a simple component who is using localstorage, i want to compare an atribute like this
test("data is added into local storage", () => {
const mockId = "111";
const mockJson = { data: "json data" };
expect(localStorage.getItem('STORE')).toEqual(JSON.stringify(mockJson));
});
but it say me :
localStorage.getItem(mockId) is undefined
i mocked like this
beforeAll(() => {
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
clear: jest.fn()
};
Object.defineProperty(window, 'localStorage', { value: localStorageMock });
})
btw this test passed, but it does not tell nothing to me, i would like to compare the values
it('renders correctly', () => {
console.log('112233',localStorage.getItem('STORE'))
expect(localStorage.getItem).toHaveBeenCalledWith('STORE')
})
You can mock it like this:
const mockGetItem = jest.fn();
Object.defineProperty(window, "localStorage", {
value: {
getItem: () => {
return mockGetItem();
},
},
});
test("data is added into local storage", () => {
const mockJson = { data: "json data" };
mockGetItem.mockReturnValueOnce(JSON.stringify(mockJson));
expect(window.localStorage.getItem("STORE")).toEqual(
JSON.stringify(mockJson)
);
});
The test itself only tests if mock is working ;) Maybe it will help you with your test.
Here you can read my post on mocking window object Mocking window object with Jest

Mock axios create in test file. React, typescript

I've seen some similar posts about mocking axios but I have spend some hours and I didn't manage to solve my problem and make my test work. I've tried solutions that I have found but they didn't work.
I'm writing small app using React, Typescript, react-query, axios. I write tests with React Testing Library, Jest, Mock Service Worker.
To test delete element functionality I wanted just to mock axios delete function and check if it was called with correct parameter.
Here is the PROBLEM:
I'm using axios instance:
//api.ts
const axiosInstance = axios.create({
baseURL: url,
timeout: 1000,
headers: {
Authorization: `Bearer ${process.env.REACT_APP_AIRTABLE_API_KEY}`,
},
//api.ts
export const deleteRecipe = async (
recipeId: string
): Promise<ApiDeleteRecipeReturnValue> => {
try {
const res = await axiosInstance.delete(`/recipes/${recipeId}`);
return res.data;
} catch (err) {
throw new Error(err.message);
}
};
});
//RecipeItem.test.tsx
import axios, { AxiosInstance } from 'axios';
jest.mock('axios', () => {
const mockAxios = jest.createMockFromModule<AxiosInstance>('axios');
return {
...jest.requireActual('axios'),
create: jest.fn(() => mockAxios),
delete: jest.fn(),
};
});
test('delete card after clicking delete button ', async () => {
jest
.spyOn(axios, 'delete')
.mockImplementation(
jest.fn(() =>
Promise.resolve({ data: { deleted: 'true', id: `${recipeData.id}` } })
)
);
render(
<WrappedRecipeItem recipe={recipeData.fields} recipeId={recipeData.id} />
);
const deleteBtn = screen.getByRole('button', { name: /delete/i });
user.click(deleteBtn);
await waitFor(() => {
expect(axios.delete).toBeCalledWith(getUrl(`/recipes/${recipeData.id}`));
});
});
In test I get error "Error: Cannot read property 'data' of undefined"
However if I would not use axios instance and have code like below, the test would work.
//api.ts
const res = await axios.delete(`/recipes/${recipeId}`);
I'm pretty lost and stuck. I've tried a lot of things and some answers on similar problem that I've found on stackoverflow, but they didn't work for me. Anybody can help?
I don't want to mock axios module in mocks, only in specific test file.
I don't have also experience in Typescript and testing. This project I'm writing is to learn.
I found some workaround and at least it's working. I moved axiosInstance declaration to a separate module and then I mocked this module and delete function.
//RecipeItem.test.tsx
jest.mock('axiosInstance', () => ({
delete: jest.fn(),
}));
test('delete card after clicking delete button and user confirmation', async () => {
jest
.spyOn(axiosInstance, 'delete')
.mockImplementation(
jest.fn(() =>
Promise.resolve({ data: { deleted: 'true', id: `${recipeData.id}` } })
)
);
render(
<WrappedRecipeItem recipe={recipeData.fields} recipeId={recipeData.id} />
);
const deleteBtn = screen.getByRole('button', { name: /delete/i });
user.click(deleteBtn);
await waitFor(() => {
expect(axiosInstance.delete).toBeCalledWith(`/recipes/${recipeData.id}`);
});
});
If you have a better solution I would like to see it.

Testing amplify Auth with Jest + Enzyme

I am very new to testing and I finally feel like I've got the hang of it. However, mocks are still a bit confusing. I am currently testing a signup function and the functions executes up until Auth.signUp. I'm not sure if I need to mock something in my test or if I need it to run through a different test.
async function signUp(
{ first, last, email, password }: SignupUserType,
dispatch: Dispatcher,
formContent: FormContentType,
setFormContent: SetFormContent,
) {
console.log('signing in init...');
dispatch({ type: 'INIT' });
try {
const user = await Auth.signUp({
username: email,
password,
attributes: {
given_name: first,
family_name: last,
picture: userImage,
},
});
console.log('sign up success!');
dispatch({ type: 'STOP_LOADING' });
console.log(formContent);
setFormContent(formContent);
} catch (err) {
console.log('error signing up...', err);
dispatch({ type: 'ERROR', error: err.message, doing: 'SIGNUP' });
}
}
Test
import Amplify, { Auth } from 'aws-amplify';
import awsconfig from '../../../aws-exports';
Amplify.configure(awsconfig);
jest.mock('aws-amplify');
it('SIGNUP: Completed form fields enable button', async () => {
...
wrapper
.find('#submitButton')
.at(0)
.simulate('click');
// thought I could do something like from https://stackoverflow.com/questions/51649891/how-to-mock-aws-library-in-jest
Auth.signUp = jest.fn().mockImplementation(
() => {
// return whatever you want to test
});
// or I tried something like from https://markpollmann.com/testing-react-applications
expect(Amplify.Auth.signUp).toHaveBeenCalled();
// kept getting errors about not receiving the call
})
I got it working!
import Amplify, { Auth } from 'aws-amplify';
import awsconfig from '../../../aws-exports';
Amplify.configure(awsconfig);
Auth.signUp = jest.fn().mockImplementation(
() => {
return true;
});
it('SIGNUP: Completed form fields enable button', async () => {
...
wrapper
.find('#submitButton')
.at(0)
.simulate('click');
})
thanks for the tip!
However, in my case implementation of Auth.forgotPassword mock has to return new Promise, because in the code I use it like
Auth.forgotPassword(email)
.then(...)
.catch(...)
so my mock is:
Auth.forgotPassword = jest.fn().mockImplementation(() => {
return new Promise((resolve, reject) => {
resolve({
CodeDeliveryDetails: {
AttributeName: 'email',
DeliveryMedium: 'EMAIL',
Destination: 's***#y***.ru',
},
})
})
})
Hope it helps someone who will stumble with the same issue
Another option would be to use:
jest.spyOn(Auth, "signUp").mockImplementation(() => {});
Through this you can omit jest.mock('aws-amplify'); and only need to import "Auth" and not Amplify itself

Testing Observable epic which invokes other epic

I am trying to test a Redux Observable epic which dispatches an action to invoke an other epic. Somehow the invoked epic is not called.
Lets say my epics looks like this;
const getJwtEpic = (action$, store, { api }) =>
action$.ofType('GET_JWT_REQUEST')
.switchMap(() => api.getJWT()
.map(response => {
if (response.errorCode > 0) {
return {
type: 'GET_JWT_FAILURE',
error: { code: response.errorCode, message: response.errorMessage },
};
}
return {
type: 'GET_JWT_SUCCESS',
idToken: response.id_token,
};
})
);
const bootstrapEpic = (action$, store, { api }) =>
action$.ofType('BOOTSTRAP')
.switchMap(() =>
action$.filter(({ type }) => ['GET_JWT_SUCCESS', 'GET_JWT_FAILURE'].indexOf(type) !== -1)
.take(1)
.mergeMap(action => {
if (action.type === 'GET_JWT_FAILURE') {
return Observable.of({ type: 'BOOTSTRAP_FAILURE' });
}
return api.getProfileInfo()
.map(({ profile }) => ({
type: 'BOOTSTRAP_SUCCESS', profile,
}));
})
.startWith({ type: 'GET_JWT_REQUEST' })
);
When I try to test the bootstrapEpic in Jest with the following code;
const response = {};
const api = { getJWT: jest.fn() };
api.getJWT.mockReturnValue(Promise.resolve(response));
const action$ = ActionsObservable.of(actions.bootstrap());
const epic$ = epics.bootstrapEpic(action$, null, { api });
const result = await epic$.toArray().toPromise();
console.log(result);
The console.log call gives me the following output;
[ { type: 'GET_JWT_REQUEST' } ]
Somehow the getJwtEpic isn't called at all. I guess it has something to do with the action$ observable not dispatching the GET_JWT_REQUEST action but I can't figure out why. All help is so welcome!
Assuming actions.rehydrate() returns an action of type BOOTSTRAP and the gigya stuff is a typo,
getJwtEpic isn't called because you didn't call it yourself 🤡 When you test epics by manually calling them, then it's just a function which returns an Observable, without any knowledge of the middleware or anything else. The plumbing that connects getJwtEpic as part of the root epic, and provides it with (action$, store) is part of the middleware, which you're not using in your test.
This is the right approach, testing them in isolation, without redux/middleware. 👍 So you test each epic individually, by providing it actions and dependencies, then asserting on the actions it emits and the dependencies it calls.
You'll test the success path something like this:
const api = {
getProfileInfo: () => Observable.of({ profile: 'mock profile' })
};
const action$ = ActionsObservable.of(
actions.rehydrate(),
{ type: 'GET_JWT_SUCCESS', idToken: 'mock token' }
);
const epic$ = epics.bootstrapEpic(action$, null, { api });
const result = await epic$.toArray().toPromise();
expect(result).toEqual([
{ type: 'GET_JWT_REQUEST' },
{ type: 'BOOTSTRAP_SUCCESS', profile: 'mock profile' }
]);
Then you'll test the failure path in another test by doing the same thing except giving it GET_JWT_FAILURE instead of GET_JWT_SUCCESS. You can then test getJwtEpic separately as well.
btw, ofType accepts any number of types, so you can just do action$.ofType('GET_JWT_SUCCESS', 'GET_JWT_FAILURE')

Resources