Reactjs – 如何在Vitest中单元测试登录表单?

huangapple go评论97阅读模式
英文:

Reactjs - How to unit test login form in Vitest?

问题

我正在使用React App,并使用ViteVitest进行单元测试。

我需要测试以下情况在登录表单上:

  • 输入的必填验证。 //工作正常
  • 无效的用户名和密码(来自API的响应)。 //工作正常
  • 登录成功。 //不符合预期

在登录成功时,它会导航个人资料页。在单元测试案例中,似乎导航不正常。以下是我的尝试。

> React:v18,Vite:4.4.4,Vitest:0.33

使用dummyjson API进行测试

> 测试文件:__tests__/Login/Validation.test.jsx

import { describe, test } from 'vitest';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import LoginPage from '../../src/pages/proteced-pages/LoginPage';
import { Provider } from 'react-redux';
import Store from '../../src/redux/store.js';
import { BrowserRouter} from 'react-router-dom';

const renderLoginPage = () => {
    render(
        <Provider store={Store()}>
            <BrowserRouter>
               <LoginPage />
            </BrowserRouter>
        </Provider>
    );
};

describe('测试 ==> 登录表单', () => {

    // 测试 1
    test('客户端验证必填):', async () => {
        renderLoginPage();

        const submitButton = screen.getByRole('button', { name: /login/i });

        fireEvent.click(submitButton);

        await waitFor(() => {
            const emailError = screen.getByText('用户名必填', { selector: 'span' });
            const passError = screen.getByText('密码必填', { selector: 'span' });

            expect(emailError).toBeInTheDocument();
            expect(passError).toBeInTheDocument();
        });
    });

    // 测试 2
    test('无效的用户名密码', async () => {
        renderLoginPage();

        const emailInput = screen.getByLabelText('用户名');
        const passwordInput = screen.getByLabelText('密码');
        const submitButton = screen.getByRole('button', { name: /login/i });

        fireEvent.change(emailInput, { target: { value: 'Sam12_#' } });
        fireEvent.change(passwordInput, { target: { value: 'pass' } });
        fireEvent.click(submitButton);

        await waitFor(() => {
            const invalidMessage = screen.getByText('无效的凭据', { selector: 'strong' });
            expect(invalidMessage).toBeInTheDocument();
        });
    });

    // 测试 3 不起作用
    test('有效的用户名密码', async () => {
        renderLoginPage();
        screen.debug();

        const emailInput = screen.getByLabelText('用户名');
        const passwordInput = screen.getByLabelText('密码');
        const submitButton = screen.getByRole('button', { name: /login/i });

        fireEvent.change(emailInput, { target: { value: 'kminchelle' } });
        fireEvent.change(passwordInput, { target: { value: '0lelplR' } });
        fireEvent.click(submitButton);

        expect(emailInput.value).toBe('kminchelle');
        expect(passwordInput.value).toBe('0lelplR');

        await waitFor(() => {
            const profilePageHeading = screen.getByText('个人资料页', { selector: 'p' });
            console.log(profilePageHeading)
            expect(profilePageHeading).toBeInTheDocument();
        });
    });
});

错误消息

> 无法找到文本为“个人资料页”的元素,匹配选择器'p'。这可能是因为文本被多个元素分隔。在这种情况下,您可以为文本匹配器提供一个函数,以使匹配更灵活。

英文:

I am working on React App with Vite and Vitest for unit testing.

I Need to test the following cases on the Login Form

  • Required validation on input. `// working fine
  • Invalid username, and password (response coming form API). `// working fine
  • Login Success. `// Not working as expected

On login success, it is navigating to Profile Page. When it comes to Unit test case it seems like it is not navigating properly. Here is what i Have tried so.

> React: v18, Vite: 4.4.4, Vitest: 0.33

Using dummyjson api for testing purpose

> Test file : __tests__/Login/Validation.test.jsx

import { describe, test } from 'vitest';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import LoginPage from '../../src/pages/proteced-pages/LoginPage';
import { Provider } from 'react-redux';
import Store from '../../src/redux/store.js';
import { BrowserRouter} from 'react-router-dom';

const renderLoginPage = () => {
    render(
        <Provider store={Store()}>
            <BrowserRouter>
               <LoginPage />
            </BrowserRouter>
        </Provider>
    );
};

describe('Test ==> Login Form:', () => {

    // TEST 1
    test('Client side validation (REQUIRED): ', async () => {
        renderLoginPage();

        const submitButton = screen.getByRole('button', { name: /login/i });

        fireEvent.click(submitButton);

        await waitFor(() => {
            const emailError = screen.getByText('username is required.', { selector: 'span' });
            const passError = screen.getByText('Password is required.', { selector: 'span' });

            expect(emailError).toBeInTheDocument();
            expect(passError).toBeInTheDocument();
        });
    });

    // TEST 2
    test('Invalid username, password: ', async () => {
        renderLoginPage();

        const emailInput = screen.getByLabelText('username:');
        const passwordInput = screen.getByLabelText('Password:');
        const submitButton = screen.getByRole('button', { name: /login/i });

        fireEvent.change(emailInput, { target: { value: 'Sam12_#' } });
        fireEvent.change(passwordInput, { target: { value: 'pass' } });
        fireEvent.click(submitButton);

        await waitFor(() => {
            const invalidMessage = screen.getByText('Invalid credentials', { selector: 'strong' });
            expect(invalidMessage).toBeInTheDocument();
        });
    });

    // TEST 3 Not working
    test('Valid username, password: ', async () => {
        renderLoginPage();
        screen.debug();

        const emailInput = screen.getByLabelText('username:');
        const passwordInput = screen.getByLabelText('Password:');
        const submitButton = screen.getByRole('button', { name: /login/i });

        fireEvent.change(emailInput, { target: { value: 'kminchelle' } });
        fireEvent.change(passwordInput, { target: { value: '0lelplR' } });
        fireEvent.click(submitButton);

        expect(emailInput.value).toBe('kminchelle');
        expect(passwordInput.value).toBe('0lelplR');

        await waitFor(() => {
            const profilePageHeading = screen.getByText('Profile pages', { selector: 'p' });
            console.log(profilePageHeading)
            expect(profilePageHeading).toBeInTheDocument();
        });
    });
});

Error message

> Unable to find an element with the text: Profile pages, which matches selector 'p'. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

答案1

得分: 0

getByText 是同步的,即它不会等待元素出现。请使用 findByText (await findByText(//...) 替代。

英文:

getByText is synchronous, i.e. it doesn't "wait" for the element to appear. Use findByText (await findByText(//...) instead.

答案2

得分: 0

我已经用MemoryRouter替换了BrowserRouter来解决问题。

>如果有更好的建议,欢迎分享。

以下是对我有效的解决方案

// 导入个人资料页面
import ProfilePage from '../../src/pages/auth-pages/ProfilePage';
// 用MemoryRouter替换BrowserRouter
const renderLoginPage = () => {
    render(
        <Provider store={Store()}>
            <MemoryRouter initialEntries={['/login']}>
                <Routes>
                    <Route path="/login" element={<LoginPage />} />
                    <Route path="/profile" element={<ProfilePage />} />
                </Routes>
            </MemoryRouter>
        </Provider>
    );
};

describe('Test ==> 登录表单:', () => {

    // 测试 3
    test('有效的用户名和密码: ', async () => {
        renderLoginPage();
        const emailInput = screen.getByLabelText('用户名:');
        const passwordInput = screen.getByLabelText('密码:');
        const submitButton = screen.getByRole('button', { name: /登录/i });

        fireEvent.change(emailInput, { target: { value: 'kminchelle' } });
        fireEvent.change(passwordInput, { target: { value: '0lelplR' } });
        fireEvent.click(submitButton);

        expect(emailInput.value).toBe('kminchelle');
        expect(passwordInput.value).toBe('0lelplR');

        await waitFor(() => {
            const profilePageHeading = screen.getByText('个人资料页面', { selector: 'p' });
            expect(profilePageHeading).toBeInTheDocument();
        });
    });
});
英文:

I have fixed the problem replacing BrowserRouter with MemoryRouter

>If someone has better suggestion then he is welcome to share.

Here is the solution that worked for me

//import profile page

import ProfilePage from &#39;../../src/pages/auth-pages/ProfilePage&#39;;
//replacing BrowserRouter with MemoryRouter

const renderLoginPage = () =&gt; {
    render(
        &lt;Provider store={Store()}&gt;
            &lt;MemoryRouter initialEntries={[&#39;/login&#39;]}&gt;
                &lt;Routes&gt;
                    &lt;Route path=&quot;/login&quot; element={&lt;LoginPage /&gt;} /&gt;
                    &lt;Route path=&quot;/profile&quot; element={&lt;ProfilePage /&gt;} /&gt;
                &lt;/Routes&gt;
            &lt;/MemoryRouter&gt;
        &lt;/Provider&gt;
    );
};

describe(&#39;Test ==&gt; Login Form:&#39;, () =&gt; {

    // TEST 3
    test(&#39;Valid username, password: &#39;, async () =&gt; {
        renderLoginPage();
        const emailInput = screen.getByLabelText(&#39;username:&#39;);
        const passwordInput = screen.getByLabelText(&#39;Password:&#39;);
        const submitButton = screen.getByRole(&#39;button&#39;, { name: /login/i });

        fireEvent.change(emailInput, { target: { value: &#39;kminchelle&#39; } });
        fireEvent.change(passwordInput, { target: { value: &#39;0lelplR&#39; } });
        fireEvent.click(submitButton);

        expect(emailInput.value).toBe(&#39;kminchelle&#39;);
        expect(passwordInput.value).toBe(&#39;0lelplR&#39;);

        await waitFor(() =&gt; {
            const profilePageHeading = screen.getByText(&#39;Profile pages&#39;, { selector: &#39;p&#39; });
            expect(profilePageHeading).toBeInTheDocument();
        });
    });
});

huangapple
  • 本文由 发表于 2023年7月24日 19:27:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76754014.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定