如何在简单的React应用中通过模拟方法为API调用编写单元测试

488 阅读6分钟

简介

编写单元测试对于开发过程是非常重要的。测试使用HTTP请求的组件有时可能是一个真正的痛苦。

在测试中,我们经常想通过模拟请求来测试我们的代码,而不需要实际进行HTTP请求。当我们在测试进行外部API调用的代码时,这可能特别重要,因为我们不想依赖外部API的可用性。

我们将使用一个名为nock的第三方软件包,它可以帮助我们模拟HTTP请求。通过nock,我们可以指定我们的模拟HTTP请求的所需行为,包括URL、头文件和正文。这使我们能够针对已知的数据集测试我们的代码,从而使调试和测试更加直接。

我将展示如何在简单的React应用中通过模拟方法为API调用编写单元测试。

我们将涵盖的步骤:

  • 为什么在测试过程中嘲弄HTTP请求是很重要的?
  • 什么是Nock?
  • 引导示例应用程序
  • 添加一个单元测试
  • Nock的安装和配置
  • Nock中的自定义请求
    • 所有的HTTP方法如GET,POST,PUT,DELETE 都可以被模拟。
    • 为了处理查询参数,可以使用query 选项。
    • 嘲弄服务器错误
  • 在Nock中记录
  • 替代的API嘲弄库

为什么在测试中模拟HTTP请求很重要?

模拟测试是加速运行测试的一个好方法,因为你可以消除外部系统和服务器。

这些都是你在用API运行测试时可能遇到的错误:

  • 每次请求时,从API返回的数据可能是不同的。
  • 需要较长的时间来完成运行测试。
  • 你可能会得到一个大的数据,而你不需要在测试中使用。
  • 你可能会有诸如速率限制和连接的问题。

我们将使用Nock来寻找这些问题的解决方案。我们将创建一个简单的react应用并请求一个外部API。我们将实现如何模拟API调用,并在React应用中使用Nock为API调用写一个单元测试。

什么是Nock?

Nock是一个HTTP服务器嘲弄和期望库。Nock通过重写Node的http.request 函数来工作。

它帮助我们模拟对API的调用,并指定我们要监听的URL,并以预定义的响应来回应,就像真正的API一样。

我们可以使用nock来测试发出HTTP请求的React组件。

引导示例应用程序

我们将使用superplateCLI向导来快速创建和定制React应用程序。

运行以下命令:

npx superplate-cli example-app

在进行CLI步骤时,选择以下选项:

? Select your project type
❯ react

? Testing Framework
❯ React Testing Library

CLI应该创建一个项目并安装选定的依赖。

用下面的代码创建一个组件:

//index.tsx
export const Main = () => {
     const [state, setState] = React.useState<{ firstName: string }>({
        firstName: '',
    });

    const fetchData = async () => {
        const response = await fetch(
            'https://api.fake-rest.refine.dev/users/1'
        );
        const result = await response.json();
        return result;
    };

    React.useEffect(() => {
        (async () => {
            const data = await fetchData();
            setState(data);
        })();
    }, []);

     return <div>{state.firstName}</div>;
};

上面我们可以看到,我们对refine的假REST API URL进行了fetch调用,此后返回的数据显示在屏幕上。

添加一个单元测试

现在,我们要创建一个测试文件。

我们要为向URL发出HTTP请求并返回所提供的数据的函数添加一个测试案例。等待API返回的数据在屏幕上呈现是一种典型的测试方式。

使用React测试库,预期的单元测试花瓶将是这样的:

//index.spec.tsx
import { Main } from './index';
import { render, screen, waitFor } from '@testing-library/react';

describe('expectedData', () => {
    it('checks if returned data from API rendered into component', async () => {
        render(<Main />);

        await waitFor(() => {
            expect(screen.getByText("/value from the api")).toBeInTheDocument();
        });
    });
});

在这一点上,如果运行测试,它将失败。它将试图执行一个网络请求。由于我们正在调用一个真正的数据库,它将返回所有的数据,而不是只返回我们需要的特定数据。
另外,API将对每个请求做出不同的响应值。

以这种方式测试这种与HTTP请求相关的架构会让人头疼。

通过nock模拟服务,我们可以拦截对API的请求并返回自定义的响应。

Nock的安装和配置

如果你没有nock,用下面的命令安装它:

npm install --save-dev nock

我们将添加突出显示的代码来初始化nock:

//index.spec.tsx
import { Main } from './index';
import { render, screen, waitFor } from '@testing-library/react';
//===>
import nock from 'nock';
//<===

describe('expectedData', () => {
    it('checks if returned data from API rendered into component', async () => {
        //===>
        nock('https://api.fake-rest.refine.dev')
            .defaultReplyHeaders({
                'access-control-allow-origin': '*',
            })
            .get('/users/1')
            .reply(200, {
                id: 1,
                firstName: "/value from the api",
            });
        //<===

        render(<Main />);

        await waitFor(() => {
            expect(
                screen.getByText("/value from the api")
            ).toBeInTheDocument();
        });
    });
});

在这一点上,我们的测试工作:

test-runs

测试运行器用nock创建了一个模拟服务器,fetchData() 方法将被触发。我们
不是调用API来测试我们的应用程序,而是提供一组已知的响应来模拟它。

Nock拦截GET ,请求到'https://api.fake-rest.refine.dev' ,然后是路径'/users/1' ,HTTP方法get

响应应该是像在reply() 方法中定义的那样。
我们还将CORS 策略设置为头信息,即defaultReplyHeaders

Nock中的自定义请求

我们可以指定模拟请求。

所有的HTTP方法如GET,POST,PUT,DELETE 都可以被模拟。

简单的POST 请求:

nock('https://api.fake-rest.refine.dev')
    .post('/users', {
         username: 'test',
         status: true,
    })
    .reply(201);

为了处理查询参数,可以使用query 选项:

nock('https://api.fake-rest.refine.dev')
    .get('/users')
    .query({
         username: 'test',
         status: true,
    })
    .reply(200);

当一个HTTP请求以指定的查询进行时,nock将拦截并返回一个200 状态代码。

嘲弄服务器的错误

错误回复可以通过replyWithError prop从嘲讽服务器返回:

nock('https://api.fake-rest.refine.dev')
    .get('/users')
    .replyWithError({
            message: 'Server ERROR',
    });

你可能想通过只回复一个状态码来处理错误:

nock('https://api.fake-rest.refine.dev')
    .post('/users', {
         username: 'test',
         status: true,
    })
    .reply(500);

注意:需要注意的是,我们使用afterAll(nock.restore)afterEach(nock.cleanAll) 来确保拦截器不会互相干扰:

afterAll(() => {
    nock.cleanAll();
    nock.restore();
});

Nock中的记录

录音依赖于拦截真实的请求和响应,然后将其持久化以备后用。

Nock将代码打印到控制台,我们可以用nock.recorder.rec() 方法将其作为测试中的一个响应。

注释掉nock函数,让我们在测试文件中加入nock.recorder.rec()

当测试运行时,控制台会记录nock所记录的所有服务调用。

nock-record

与其手动定义nock 方法和回复值,我们可以使用记录的值。

替代的API嘲弄库

MSW Mock Service Worker。Mock Service Worker是一个API嘲讽库,它使用Service Worker API来拦截实际请求。

Mirage JS:Mirage JS是一个API嘲讽库,它可以让你建立、测试和分享一个完整的工作的JavaScript应用程序,而不需要依赖任何后台服务。

fetch-mock:fetch-mock允许模拟使用fetch或模仿其API的库发出的HTTP请求。

总结

在这篇文章中,我们已经实现了API嘲讽,并解释了它是多么有用。我们在测试中使用了nock来模拟HTTP请求,并展示了一些有用的属性。

我们已经看到了如何孤立地只测试一个应用程序的行为。避免任何可能影响我们测试的外部依赖关系,并确保它们一直运行在稳定的版本上。

构建你的基于React的CRUD应用,不受约束

低代码的React框架对于获得开发速度是很好的,但如果你的项目需要广泛的造型和定制,它们往往在灵活性方面有所欠缺。


refine blog logo

refine是一个基于React的框架,用于构建无约束的CRUD应用程序。 它可以将你的开发时间加快3倍,而不影响造型定制项目工作流程