Sitemap

My React Native Journey: From Overusing Redux to Smarter State Management 4

3 min readMar 23, 2025

--

Press enter or click to view image in full size
Photo by Oskar Yildiz on Unsplash

Oke now lets dive to Jest. To mock using Jest we have 2 ways directly mock in the test file and default mock using __mocks__ folder.

Direct Mock

First we will using direct mock in the test file, as you can see in the sample bellow, we want to mock firebase/auth.

So first step is define what will we mock

jest.mock("firebase/auth");

then we can mock using mockResolvedValue like bellow

(signInWithEmailAndPassword as jest.Mock).mockResolvedValue({
user: { uid: "67890" },
});

thats mean we want to mock signInWithEmailAndPassword from firebase/auth and returning value user with uid 67890.

Full code will be like this

import {
createUserWithEmailAndPassword,
onAuthStateChanged,
signInWithEmailAndPassword,
signOut,
getAuth,
} from "firebase/auth";

jest.mock("firebase/auth");

describe("Firebase Auth Mocking", () => {
test("should mock createUserWithEmailAndPassword", async () => {
(createUserWithEmailAndPassword as jest.Mock).mockResolvedValue({
user: { uid: "12345" },
});

const auth = getAuth();
const userCredential = await createUserWithEmailAndPassword(
auth,
"test@example.com",
"password"
);

expect(createUserWithEmailAndPassword).toHaveBeenCalledWith(
auth,
"test@example.com",
"password"
);
expect(userCredential.user.uid).toBe("12345");
});

test("should mock signInWithEmailAndPassword", async () => {
(signInWithEmailAndPassword as jest.Mock).mockResolvedValue({
user: { uid: "67890" },
});

const auth = getAuth();
const userCredential = await signInWithEmailAndPassword(
auth,
"test@example.com",
"password"
);

expect(signInWithEmailAndPassword).toHaveBeenCalledWith(
auth,
"test@example.com",
"password"
);
expect(userCredential.user.uid).toBe("67890");
});

test("should mock signOut", async () => {
(signOut as jest.Mock).mockResolvedValue(undefined);

const auth = getAuth();
await signOut(auth);

expect(signOut).toHaveBeenCalledWith(auth);
});

test("should mock onAuthStateChanged", () => {
const mockCallback = jest.fn();
(onAuthStateChanged as jest.Mock).mockImplementation((auth, callback) => {
callback({ uid: "99999" }); // Simulate a user being logged in
return () => {}; // Mock unsubscribe function
});

const auth = getAuth();
onAuthStateChanged(auth, mockCallback);

expect(mockCallback).toHaveBeenCalledWith({ uid: "99999" });
});
});

Mock with __mocks__ folder

To achieve this we need to create __mocks__ folder in the root.

Then we need create folder again according to import, let say we want to mock firebase/auth so we need create folder something like this.

- root
- __mocks__
- firebase
- auth.ts

then we can mock all method in the auth.ts that we create before.

export const createUserWithEmailAndPassword = jest.fn((auth, email, password) =>
Promise.resolve({
user: {
displayName: "New User",
email,
phoneNumber: null,
photoURL: null,
},
})
);
export const signInWithEmailAndPassword = jest.fn((auth, email, password) => {
if (email === "wrong@example.com") {
return Promise.reject(new Error("Invalid credentials"));
}

return Promise.resolve({
user: {
displayName: "Test User",
email,
phoneNumber: null,
photoURL: null,
},
});
});
export const onAuthStateChanged = jest.fn();
export const signOut = jest.fn();
export const getAuth = jest.fn();

As you can see we mock

  • createUserWithEmailAndPassword
  • signInWithEmailAndPassword
  • onAuthStateChanged
  • signOut
  • getAuth

Then on the test (I will give you another sample that different from before), we dont need to mockResolvedValue again because Jest will search mock in the folder that we created.

import { useAuthStore } from "@/stores/authStore";
import { renderHook } from "@testing-library/react-native";
import { act } from "react";

jest.mock("firebase/app");
jest.mock("firebase/auth");

describe("Auth Store", () => {
beforeEach(() => {
jest.clearAllMocks(); // Prevents previous test leaks
});

it("should start with default state", () => {
const { result } = renderHook(() => useAuthStore());

expect(result.current.user).toBeNull();
expect(result.current.loading).toBe(false);
expect(result.current.error).toBeNull();
});

it("should log in a user successfully", async () => {
const { result } = renderHook(() => useAuthStore());

await act(async () => {
await result.current.login("test@example.com", "password123");
});

expect(result.current.user?.email).toEqual("test@example.com");
expect(result.current.loading).toBe(false);
expect(result.current.error).toBeNull();
});

it("should register a user successfully", async () => {
const { result } = renderHook(() => useAuthStore());

await act(async () => {
await result.current.register("new@example.com", "password123");
});

expect(result.current.user?.email).toEqual("new@example.com");
});

it("should fail login with incorrect credentials", async () => {
const { result } = renderHook(() => useAuthStore());

await act(async () => {
await result.current.login("wrong@example.com", "wrongpassword");
});

expect(result.current.user).toBeNull();
expect(result.current.error).toBe("Invalid credentials");
});

it("should log out the user", async () => {
const { result } = renderHook(() => useAuthStore());

await act(async () => {
await result.current.login("test@example.com", "password123");
await result.current.logout();
});

expect(result.current.user).toBeNull();
});
});

But we can also change default mock that we define before using mockResolvedValueOnce. That mean we will return custom mock once and the next we call the method will return default method again.

--

--