Search code examples
reactjsjestjssupabase

Mocking supabase-js supabase.auth.signUp


I want to mock supabase.auth.signUp of @supabase/supabase-js. I am not directly calling supabase.auth.signUp, but in a wrapper I created. If I don't use jest.fn(), but a default JS function, it is correctly resolving the object I want. However this does not give me the flexibility of resolving with different values in different tests...

src/supabase/supabase.ts

import { createClient } from "@supabase/supabase-js";

export const supabase = createClient(
  process.env.REACT_APP_SUPABASE_URL as string,
  process.env.REACT_APP_SUPABASE_ANON_KEY as string
);

src/supabase/__mocks__/supabase.ts

// This default function does work...
// export const supabase = {
//   auth: {
//     signUp: () =>
//       Promise.resolve({ data: { user: null, session: null }, error: null }),
//   },
// };


// This jest.fn() does not work and returns undefined?
export const supabase = {
  auth: {
    signUp: jest
      .fn()
      .mockResolvedValue({ data: { user: null, session: null }, error: null }),
      // mockImplementation does the same
      // .mockImplementation(() =>
      //   Promise.resolve({ data: { user: null, session: null }, error: null })
      // ),

  },
};

src/supabase/signUp.ts

import Auth from "../interfaces/Auth";
import Credentials from "../interfaces/Credentials";
import Error from "../interfaces/Error";
import { supabase } from "./supabase";


export default async function signUp(
  credentials: Credentials
): Promise<{ auth: Auth; error: Error | null }> {
  const { data, error } = await supabase.auth.signUp({
    email: credentials.email,
    password: credentials.password,
  });

  ...
}

src/supabase/signUp.test.ts

import Credentials from "../interfaces/Credentials";
import signUp from "./signUp";

jest.mock("./supabase");

// This call does return the correct value?
console.log(supabase.auth.signUp({email: "test@example.com", password: "password"}))

test("successful sign up", async () => {
  const credentials: Credentials = {
    email: "test@example.com",
    password: "password",
  };
  const { auth, error } = await signUp(credentials);

  console.log({auth, error})

  expect(auth.user).toBeDefined();
  expect(auth.session).toBeNull();
});

Output

FAIL  src/supabase/signUp.test.ts
  ● successful sign up

    TypeError: Cannot destructure property 'data' of '(intermediate value)' as it is undefined.

       8 |   params: Credentials
       9 | ): Promise<{ auth: Auth; error: Error | null }> {
    > 10 |   const { data, error } = await supabase.auth.signUp({
         |           ^
      11 |     email: params.email,
      12 |     password: params.password,
      13 |   });

      at signUp (src/supabase/signUp.ts:10:11)
      at Object.<anonymous> (src/supabase/signUp.test.ts:12:27)

Question

How can I make supabase.auth.signUp resolve with a mocked value inside src/supabase/signUp.ts while I am executing src/supabase/signUp.test.ts?

Thanks in advance.


Solution

  • U can use jest.spyOn to spy on the signUp function, I already tried this but in a global scope, make sure to use it in a beforeEach, or in the actual test itself. beforeAll and global scope does not work

    src/supabase/signUp.test.ts

    import { supabase } from "./supabase";
    
    beforeEach(() => {
      jest
        .spyOn(supabase.auth, "signUp")
        .mockResolvedValueOnce({ data: { user: null, session: null }, error: null });
    });
    

    or...

    test("successful sign up", async () => {
      jest.spyOn(supabase.auth, "signUp").mockResolvedValueOnce({ data: { user: null, session: null }, error: null });
      ...
    });