javascriptreactjstypescriptmsw

MSW does not work in functions within contexts


I'm trying to intercept a post request with MSW to mock the result. I have a handleLogin function within a context, however, MSW cannot recognize handleLogin as a function. I get the error ReferenceError: handleLogin is not a function. If I make the api call out of context, it works.

This is my context:

export function AuthProvider({ children }: { children: ReactNode }) {

  ...

  const handleLogin = async function(event: FormEvent<HTMLFormElement>): Promise<AxiosResponse> {
    /**
     * Authenticate the user.
     * if username and password are corrects, the token refresh is stored on localStorage and
     * the isAuthenticate will be truthy.
     */
    const url = 'http://127.0.0.1:8000/api/token/';

    const response = await axios.post(url, {
      username: event.currentTarget.username.value,
      password: event.currentTarget.password.value,
    });

    return new Promise(function(resolve, reject) {
      if (response.status !== 200) return reject('unauthorized');
    
      const tokenRefresh = JSON.stringify(response.data.refresh);
      const tokenAccess = JSON.stringify(response.data.access);

      localStorage.setItem('userTokens', tokenRefresh);
      setUserTokens(tokenRefresh);
      setIsAuthenticated(tokenAccess);

      return resolve(response);
    })
  }

  return (
    <AuthContext.Provider value={{
      userTokens: userTokens,
      isAuthenticated: isAuthenticated,
      handleLogin: handleLogin,
      handleLogout: handleLogout,
    }}>
      {children}
    </AuthContext.Provider>
  )
}

this is my login form:

export default function LoginPage() {
  
  ...

  const { handleLogin, isAuthenticated } = useContext(AuthContext);

  ...

  function handleSubmit(e: FormEvent<HTMLFormElement>): void {
    e.preventDefault();
  
    if (!validateFields()) return;
    
    setLoading(true);

    handleLogin(e)
    .then(() => {
      navigate('/admin/dashboard', { replace: true });
      setLoading(true);
    })
    .catch(() => {
      setLoading(false);
      setUsernameError('username or password is invalid');
    });
  }

  ...
}

this is my test

import { render, screen, fireEvent } from '@testing-library/react';
import LoginPage from '..';
import { BrowserRouter } from 'react-router-dom';
import { userEvent } from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
 
const handlers = [
  http.post('http://127.0.0.1:8000/api/token/', () => {
    return new HttpResponse(null, { status: 401 })
  }),
]

const server = setupServer(...handlers);

function renderLoginPage() {
  return (
    render(
      <BrowserRouter>
        <LoginPage />
      </BrowserRouter>
    )
  )
}

describe('<loginPage />', () => {
  beforeAll(() => server.listen());
  afterEach(() => server.resetHandlers());
  afterAll(() => server.close());

  it('should render error message if user not found', async () => {
    renderLoginPage();

    // mock input data
    const usernameInput = screen.getByLabelText(/username/i);
    const passwordInput = screen.getByLabelText(/password/i);
    fireEvent.change(usernameInput, {target: {value: 'test'}});
    fireEvent.change(passwordInput, {target: {value: 'test'}});

    const button = await screen.findByText(/sign in/i);     

    await userEvent.click(button);
  
    await screen.findByText(/usuário ou senha inválidos/i);

  });
})

my error

enter image description here

I created a login function directly inside the login form, and everything works normally, but I need the handleLogin function within the context.


Solution

  • You need to wrap LoginPage with AuthProvider to access AuthContext.

    function renderLoginPage() {
        return (
            render(
                <BrowserRouter>
                    <AuthProvider>
                        <LoginPage />
                    </AuthProvider>
                </BrowserRouter>
            )
        )
    }