question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Requests is Node don't resolve immediately

See original GitHub issue

Environment

Name Version
msw 0.24.2
node 10.21.0
OS Mac OSX 10.15.7

Question

Pardon me if this is the wrong place to post. I am working on transferring all of our mocks to a wrapper library around your wonderful library. This is working great, but now that I have moved on to updating our UI unit tests I’ve run into a bit of a quandary.

Most of our tests do something like:

jest.spyOn(global, 'fetch').mockImplementation(url => Promise.resolve({
	json: () => Promise.resolve(data),
	status: 200	
}));

I believe this is a fairly common pattern that I’ve seen referenced many places. I’ve removed this functionality to accommodate setupServer, but now have multiple issues with act warnings and unexpected tree states. I understand that the Node requests will be intercepted in the middleware, and thus asynchronous requests are still being made with some latency. I’ve also read the “How is this library different?” section of node-request-interceptor, so understand that this particular project takes a stance.

It seems my only option at this point is to install and utilize react-testing-library’s waitFor utility. It just seems like a lot of overhead to upgrade react-scripts/Jest to get all this working. I was hoping listen would be a little more accommodating to customizations, but maybe that’s not in the cards.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:6 (4 by maintainers)

github_iconTop GitHub Comments

2reactions
evanblackcommented, Jan 11, 2021

@kettanaito Here’s an example that demonstrates the problem with the LoginForm component from the React REST examples. I know this maybe isn’t the most “ideal” testing setup.

import React, { useState, useCallback } from 'react';
import renderer from 'react-test-renderer';
import { setupServer } from 'msw/node';
import { rest } from 'msw';

export const LoginForm = () => {
  // Store the username so we can reference it in a submit handler
  const [username, setUsername] = useState('')

  // Create a state for the user data we are going to receive
  // from the API call upon form submit.
  const [userData, setUserData] = useState(null)

  // Whenever we change our username input's value
  // update the corresponding state's value.
  const handleUsernameChange = useCallback((event) => {
    setUsername(event.target.value)
  }, [])

  // Handle a submit event of the form
  const handleFormSubmit = useCallback(
    (event) => {
      // Prevent the default behavior, as we don't want
      // for our page to reload upon submit.
      event.preventDefault()

      // Perform a POST /login request and send the username
      fetch('/login', {
        method: 'POST',
        body: JSON.stringify({
          username,
        }),
      })
        .then((res) => res.json())
        // Update the state with the received response
        .then(setUserData)
				.catch((err) => console.error(JSON.stringify(err.message || err)))
    },
    [username]
  )

  if (userData) {
    return (
      <div>
        <h1>
          <span data-testid="firstName">{userData.firstName}</span>{' '}
          <span data-testid="lastName">{userData.lastName}</span>
        </h1>
        <p data-testid="userId">{userData.id}</p>
        <p data-testid="username">{userData.username}</p>
      </div>
    )
  }

  return (
    <form id="login-form" onSubmit={handleFormSubmit}>
      <div>
        <label htmlFor="username">Username:</label>
        <input
          id="username"
          name="username"
          value={username}
          onChange={handleUsernameChange}
        />
        <button type="submit">Submit</button>
      </div>
    </form>
  )
};

const handlers = [
  rest.post('/login', (req, res, ctx) => {
    const { username } = req.body

    return res(
      ctx.json({
        id: 'f79e82e8-c34a-4dc7-a49e-9fadc0979fda',
        username,
        firstName: 'John',
        lastName: 'Maverick',
      })
    )
  }),
]

const server = setupServer(...handlers)

// Establish API mocking before all tests.
beforeAll(() => {
	server.listen()
})
// Reset any request handlers that we may add during the tests,
// so they don't affect other tests.
afterEach(() => {
	server.resetHandlers()
})
// Clean up after the tests are finished.
afterAll(() => {
	server.close()
})

test('new test with msw', async () => {
	let container;

	await renderer.act(async () => {
		container = renderer.create(<LoginForm />);
	});

	await renderer.act(async () => {
		container.root.findByProps({ id: 'username' }).props.onChange({ target: { value: 'testing' }});
		container.root.findByProps({ id: 'login-form' }).props.onSubmit({ preventDefault: () => {} });
		// This line will make it work since it's enough time for the request to come back from Node
		// await new Promise(resolve => setTimeout(resolve, 100));
	});

	expect(container.toJSON()).toMatchSnapshot();

});

test('original test with immediate resolution', async () => {
	let container;
	jest.spyOn(global, 'fetch').mockImplementationOnce(url => Promise.resolve({
		json: () => Promise.resolve({
			id: 'f79e82e8-c34a-4dc7-a49e-9fadc0979fda',
			username: 'testing',
			firstName: 'John',
			lastName: 'Maverick',
		}),
		status: 200
	}));

	await renderer.act(async () => {
		container = renderer.create(<LoginForm />);
	});

	await renderer.act(async () => {
		container.root.findByProps({ id: 'username' }).props.onChange({ target: { value: 'testing' }});
		container.root.findByProps({ id: 'login-form' }).props.onSubmit({ preventDefault: () => {} });
	});

	expect(container.toJSON()).toMatchSnapshot();
	global.fetch.mockRestore();
});

1reaction
msutkowskicommented, Feb 13, 2021

Wouldn’t you have to await the form submission being that it is an async handler? I think mocking fetch was just masking the promise-related issues here, but I could be wrong!

await container.root.findByProps({ id: 'login-form' }).props.onSubmit({ preventDefault: () => {} });

Edit: I was curious about this, so I made a small repro where it works as expected based on the MSW template.

https://codesandbox.io/s/msw-test-react-test-renderer-qpd48?file=/test/App.test.js

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to resolve a request in Node js - Stack Overflow
You can make a request to the ip address and simply pass in the hostname through the headers. Share. Share a link to...
Read more >
5 ways to make HTTP requests in Node.js - LogRocket Blog
Make HTTP requests in Node.js using the native module as well as npm packages like Axios, Got, SuperAgent, and node-fetch.
Read more >
Create HTTP Request Body Using Node.js in Simple Steps
In this article, you will understand how you can make the HTTP request differently using node.js step-by-step process.
Read more >
HTTP | Node.js v19.3.0 Documentation
When a connection is closed by the client or the server, it is removed from the pool. Any unused sockets in the pool...
Read more >
Top 10 Most Common Node.js Developer Mistakes - Toptal
This means that no two parts of your application run in parallel; instead, ... For example, a request from Node.js to the database...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found