I recently needed to modify an older library to use TS
and perform unit testing. Modifying it to use TS
was not too difficult, but diving into unit testing was a whole new ball game for me. As I was just starting out with the Jest
framework, I found testing network requests a bit challenging. So, I decided to jot down some ways to Mock
Axios
to make network requests. As a newbie who's just started exploring this for a couple of days, I'd appreciate any feedback or corrections.
All the examples mentioned in this document can be found in the jest-axios-mock-server repository. You can simply install it using a package manager, for example with yarn
:
In the package.json
, there are several specified commands as follows:
npm run build
: The command for building with rollup
.npm run test:demo1
: Simple mock
encapsulation of the network request library.npm run test:demo2
: Using reimplementation and hook
to complete the mock
.npm run test:demo3
: Using the library within Jest
to achieve the implementation of demo2
.npm run test:demo4-5
: Starts a node
server and uses axios
's proxy
to proxy the network requests to the started node
server. With the corresponding unit test request and response data set up, it utilizes the corresponding relationships to achieve testing. In other words, this is the work completed by jest-axios-mock-server
.Here, we've encapsulated another layer of axios
, which is closer to the real scenario. You can check the test/demo/wrap-request.ts
file, which essentially just creates a axios
instance internally and forwards the response data. The test/demo/index.ts
file simply exports a counter
method, where there's some processing for the two parameters before making a network request, and then there's also some processing for the response data. All of this is just to simulate some related operations.
In this case, Jest
uses a JSDOM
simulated browser environment. The jest.config.js
file has the setupFiles
property configured with the startup file test/config/setup.js
, where JSDOM
is initialized.
A simple mock
operation is performed in test/demo1.test.js
, you can try running it by npm run test:demo1
. In fact, it is a mock
operation on the wrap-request
library that wraps axios
. When Jest is started, it will be compiled. Here, after mocking
this library, all the files that import this library afterwards will get the mocked
object. In other words, we can consider that this library has been rewritten, and after rewriting, the methods are all JEST
'sMock Functions
, and you can use functions like mockReturnValue
for data simulation. For more information on Mock Functions
, you can refer to https://www.jestjs.cn/docs/mock-functions
.
Here, we have completed the Mock
of the return value. This means that we can control the return value of the request
in the wrap-request
library. However, it was mentioned earlier that there is also a certain amount of processing for the input parameters, which we have not yet asserted, so we also need to try to process this part.
You can try running demo2
through npm run test:demo2
. As mentioned above, we can handle the return value, but we cannot assert whether the input parameters have been correctly processed. Fortunately, Jest
provides a way to directly implement a Mock
of the function library. In fact, Jest
also provides a way to use mockImplementation
, which is the method used in demo3
. Here, we have rewritten the function library being mocked
, and in the implementation, we can also use jest.fn
to complete the Implementations
. Here, by writing a hook
function before returning, and implementing assertions or specifying return values in each test
, we can solve the above problem, which is actually the implementation of mockImplementation
in Jest
's Mock Functions
.
To run demo3
, simply use npm run test:demo3
. The example in demo2
is actually more complicated than necessary. In Jest, Mock Functions
have an implementation called mockImplementation
, which can be directly used.
Demo4
and demo5
can be run by using npm run test:demo4-5
. This method involves actual data requests. Here, axios
is used for proxying the internal data requests to a specified server port. This server is also locally initiated and is used to test specific requests and corresponding response data. If the requested data is incorrect, it will not match the relevant response data, which will then result in a direct return of 500
. Any incorrect response data will also be caught during assertion. This is where the jest-axios-mock-server
library comes into play. First, three files need to be specified for each of the three lifecycle operations: before the start of each unit test file, before the start of Jest
testing, and after Jest
testing has completed. These files are configured in the jest.config.js
file under the setupFiles
, globalSetup
, and globalTeardown
configurations.
Firstly, the setupFiles
file, in which we initialize JSDOM
and also perform operations on the default proxy of axios
. This is necessary due to the approach of using axios
's proxy
for forwarding data requests, requiring the initialization of proxy values at the beginning of unit testing.
Next are the globalSetup
and globalTeardown
configurations, where we start and shut down the server. It is important to note that the files run in this context are entirely separate from any context of the actual unit testing or those specified in the setupFiles
configuration. This includes the exchange of data over the network between server ports.
For configuring port and domain information, they can be directly placed in the jest.config.js
file under the globals
field. Regarding the debug
configuration, it is recommended to use it in conjunction with test.only
to print relevant request information while calling server information.
Of course, there may be a question about why not start and stop the server in the beforeAll
and afterAll
lifecycles of each unit test file. First of all, I have tried this approach. Starting and stopping the server for each test file does take some time, but theoretically it's still reasonable. After all, it's correct to isolate the data. However, there was a problem when closing in the afterAll
phase. This is because the close
method called when the node
server shuts down does not actually close the server and release the port. It just stops handling requests, so the port is still in use. When starting the second unit test file, a "port in use" exception is thrown. Although there are some solutions now, my attempts did not yield ideal results. There were occasional cases where the port was still occupied, especially the first time it was run after node
was booted, with a relatively high incidence of exceptions. Therefore, the effect was not very satisfactory. In the end, I chose this completely isolated approach. For specific related issues, you can refer to https://stackoverflow.com/questions/14626636/how-do-i-shutdown-a-node-js-https-server-immediately
.
Since a completely isolated approach is used, when we want to transfer request and response data for testing, we have only two options. Either specify all the data when the server starts, that is, in the file test/config/global-setup.js
, or transfer the data over the network, i.e., during the server's operation, the specified path
will carry the data with the network request. In the server's closure, this data request will be specified. Of course, both ways are supported here, but I think it's more appropriate to specify one's own data in each unit test file, so here I only provided an example of specifying the data to be tested in the unit test file. Regarding the data to be tested, I specified a DataMapper
type to reduce exceptions caused by type errors. Here are two data sets as examples. Also, when matching query
and data
, regular expressions are supported. The structure of the DataMapper
type is quite standard.
In the final two unit tests, I specified the data to be tested in the beforeAll
phase. It's important to note that it is return setSuitesData(data)
here, because the unit tests should be conducted only after the data is successfully set. Then it's just normal requests and responses, and assertions to test correctness.