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.

Support concurrent runs in Node.js

See original GitHub issue

What

I suggest for the setupServer (node-request-interceptor) API to support execution in a concurrent mode (i.e. multiple test suite at the same time).

Why

Concurrency is crucial in large projects, allowing for faster execution of tests.

Current behavior

Currently node-request-interceptor patches native request issuing modules per process, which allows irrelevant mocking rules to leak to another test suites, as they all run in a single process.

How

Look into how nock operates. It performs modules patching in a similar fashion, and I’d suspect it to support concurrent mode. We can learn how they did it and evaluate if we can adopt a similar solution.

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:23
  • Comments:29 (20 by maintainers)

github_iconTop GitHub Comments

16reactions
kettanaitocommented, Mar 18, 2022

I’ve had some time to think about this, sharing my thoughts below.

First of all, the issue is relevant only to using runtime handlers with parallel test runs. If you’re using the same (global) set of handlers you won’t be affected: handlers are idempotent in nature. When using runtime handlers (server.use()), you modify the global list of handlers by pretending a runtime handler. Since all tests refer to the global handlers list, in parallel runs if you prepend one handler in one test, it may affect requests in the irrelevant test (module).

I can propose to solve this by ensuring identity of requests and runtime handlers.

Request identity

Currently, each captured request has an id. That’s okay. On top of that, I suggest to add something like moduleId/modulePath that represents the module which issued this request. This is only relevant in Node.js.

{
  id: "uuid-of-request",
  "modulePath": "/User/octocat/GitHub/test/Profile.test.tsx"
}

Runtime handler identity

Similar to requests identity, I propose to add moduleId/modulePath only to runtime handlers (those defined via server.use()). As long as the request and the runtime handler share the moduleId, it guarantees that they were issued/prepended in the same module and can affect each other.

// test/index.test.ts
it('some test', () => {
  // Set the "moduleId" to this "GET /foo" handler.
  server.use(rest.get('/foo', resolver))

  // Add the "moduleId" to this captured request
  // (done internally in @mswjs/interceptors). 
  fetch('/foo')
})
  • The moduleId is not set in the browser.
  • The moduleId is derived from the module and is fixed.
  • The moduleId affects what handlers are considered relevant in the lookup phase. We can first check the moduleId equality (if set at all) before executing handler.predicate().

I find the modification of request/handler instances more acceptable as opposed to creating a handlers list relevant to the module because we can keep our internal handler lookup/resolution phase intact with the proposed change—the source of handlers always remains the same.

In theory, this should allow us to support parallel test runs. Curious about what others think.

6reactions
androacommented, Jul 11, 2021

I have found two things in my test suite that creates the flaky behaviour. Simply clearing the cache like this does not help (all the time):

afterEach(async () => {
  cache.clear();
  server.resetHandlers();
});

When adding a artifical delay like this, it seems to consistently work:

afterEach(async () => {
  await new Promise((resolve) => setTimeout(resolve.bind(null), 0));
  cache.clear();
  server.resetHandlers();
});

I suspect this might be connected to the other thing I’ve found, but not been able to prove/create a consistent reproducing case.

I test a component that uses SWR to load data, but in this case it can render instantly before the fetch is complete and update it as the data comes in. I have no way to differentiate the loading status from an empty dataset, so the test passes immediately.

But the request (and the mock) is still in-flight and active, so the next test (which should have data) hits the previous mock.

Workaround was adding an invisible loader that I can wait for to disappear before passing the test.

Read more comments on GitHub >

github_iconTop Results From Across the Web

A simple guide to JavaScript concurrency in Node.js | TSH.io
In the article about JavaScript concurrency in Node.js, the author clarifies how Node deals with asynchronicity. What are the biggest traps?
Read more >
Parallelism, concurrency, and async programming in Node.js
Concurrency means that a program is able to run more than one task at a time — this is not to be confused...
Read more >
How to handle concurrency in Node.js ? - GeeksforGeeks
Node.js works asynchronously. In other words, it does not block incoming requests from clients when the operating system has one I/O ...
Read more >
How does concurrency work in nodejs? - Stack Overflow
Node.js does use multiple threads to handle io user the covers, but this is hidden from the user.
Read more >
Does Node.js support concurrent programming natively like Go?
No. Node.js concurrency is “we have only one thread running event loop”. The nearest thing to real concurrency are webworkers - isolated environments ......
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