如何使用带有玩笑和 react-testing-library 的 authcontext 测试我的受保护路由

时间:2021-04-20 18:11:28

标签: reactjs firebase-authentication jestjs mocking react-testing-library

我有一个带有受保护路由组件的 React 应用程序:

import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { AuthConsumer } from '../../Utils/contexts/AuthContext.js';

const ProtectedRoute = ({ component: Component, ...rest }) => {

  
  return (
    <AuthConsumer>
  {({ currentUser }) => (    <Route {...rest} render={
          props => {
            if ({ currentUser }) {
              return <Component {...rest} {...props} />
            } else {
              return <Redirect to={
                {
                  pathname: '/unauthorized',
                  state: {
                    from: props.location
                  }
                }
              } />
            }
          }
        } />)}
      </AuthConsumer>
  )
}

export default ProtectedRoute; 

ProtectedRoute 使用授权上下文:

import React, { useEffect, useState } from 'react';
import firebase from '../firebase';

const AuthContext = React.createContext();

export const AuthProvider = ({ children }) => {
    const [ currentUser, setCurrentUser ] = useState(null);

    useEffect(() => {
        firebase.auth().onAuthStateChanged((user) => setCurrentUser(user));
    }, [currentUser]);

    return (
        <AuthContext.Provider
            value={{
                currentUser
            }}
        >
            {children}
        </AuthContext.Provider>
    );
};

export const AuthConsumer = AuthContext.Consumer

我想用 jest 和 react-testing-library 测试当 currentUser 存在时路由是否有效,当一个不存在时路由是否有效。到目前为止,我的测试是:

import React from 'react'
import Info from '../../pages/Info/info'

import {
  render,
  screen,
  fireEvent,
} from '../../Utils/test-utils/testing-library-utils'
import ProtectedRoute from './protectedRoute'

jest.mock('../../pages/Info/info', () => {
  return jest.fn(() => null)
})

const AuthContext = React.createContext()
const currentUser = {
  uid: 1,
}

describe('protected route works with authenticated users', () => {
  test('can login when user is authenticated', () => {
    render(
      <AuthContext.Provider
        value={{
          currentUser,
        }}>
        <ProtectedRoute component={<Info />} />
      </AuthContext.Provider>
    )
    const returnedRoute = screen.queryByTestId('currentuser-valid')
    console.log('RETURNED ROUTE', returnedRoute)
    expect(returnedRoute).toBeNull()
  })
})

我似乎无法将伪造的 currentUser 对象传递给 ProtectedRoute - 我总是收到“类型错误:无法解构 'undefined' 或 'null' 的属性 currentUser。”

如何模拟 AuthContext?我该怎么做才能将有效或无效的值作为 currentUser 传递给它,这样我才能使测试通过或失败?

1 个答案:

答案 0 :(得分:0)

AuthContext 的提供者和消费者应该是一对。您不应在测试用例中创建新上下文。在 AuthContext 组件的文件和测试文件中继续使用相同的 ProtectedRoute

例如

ProtectedRoute.tsx

import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { AuthConsumer } from './AuthContext';

const ProtectedRoute = ({ component: Component, ...rest }) => {
  return (
    <AuthConsumer>
      {({ currentUser }) => (
        <Route
          {...rest}
          render={(props) => {
            console.log('currentUser: ', currentUser);
            if (currentUser) {
              return <Component {...rest} {...props} />;
            } else {
              return (
                <Redirect
                  to={{
                    pathname: '/unauthorized',
                    state: {
                      from: props.location,
                    },
                  }}
                />
              );
            }
          }}
        />
      )}
    </AuthConsumer>
  );
};

export default ProtectedRoute;

AuthContext.tsx

import React, { useState } from 'react';

interface Context {
  currentUser: { uid: number } | null;
}
export const AuthContext = React.createContext<Context>({ currentUser: null });

export const AuthProvider = ({ children }) => {
  const [currentUser, setCurrentUser] = useState(null);
  return <AuthContext.Provider value={{ currentUser }}>{children}</AuthContext.Provider>;
};

export const AuthConsumer = AuthContext.Consumer;

Info.tsx

import React from 'react';

export function Info() {
  return <div>info</div>;
}

ProtectedRoute.test.tsx

import React from 'react';
import { render, screen } from '@testing-library/react';
import ProtectedRoute from './protectedRoute';
import { AuthContext } from './AuthContext';
import { MemoryRouter } from 'react-router-dom';
import { Info } from './Info';

const currentUser = { uid: 1 };

describe('protected route works with authenticated users', () => {
  test('can login when user is authenticated', () => {
    render(
      <AuthContext.Provider value={{ currentUser }}>
        <MemoryRouter initialEntries={['/']}>
          <ProtectedRoute component={Info} />
        </MemoryRouter>
      </AuthContext.Provider>
    );
    expect(screen.queryByTestId('protected-component')).toBeDefined();
  });
});

测试结果:

 PASS  examples/67184212/ProtectedRoute.test.tsx (7.628 s)
  protected route works with authenticated users
    ✓ can login when user is authenticated (73 ms)

  console.log
    currentUser:  { uid: 1 }

      at render (examples/67184212/ProtectedRoute.tsx:12:21)

--------------------|---------|----------|---------|---------|-------------------
File                | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
--------------------|---------|----------|---------|---------|-------------------
All files           |   86.96 |       50 |      80 |      85 |                   
 AuthContext.tsx    |   71.43 |      100 |       0 |   66.67 | 9-10              
 Info.tsx           |     100 |      100 |     100 |     100 |                   
 ProtectedRoute.tsx |   92.31 |       50 |     100 |   90.91 | 16                
--------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        8.169 s

包版本:

"@testing-library/react": "^11.2.2",
"react": "^16.14.0",
"react-router-dom": "^5.2.0",