11

I have to test for server errors (Express) in acceptance tests that can't (or shouldn't) be sent with response, for example

Error: Can't set headers after they are sent.

Catching an error with error handler and responding with 5XX code would provide valuable feedback here, but the problem is that the headers have been sent already.

This kind of bugs may be noncritical and hard to spot, and usually they are figured out from the logs.

The spec is

it('should send 200', function (done) {
    request(app).get('/').expect(200, done);
});

And tested app is

app.get('/', function (req, res, next) {
    res.sendStatus(200);
    next();
});

app.use(function (req, res) {
    res.sendStatus(200);
});

What is the most appropriate way to communicate between Express app instance and request testing library (i.e. Supertest) in similar cases?

The question is not restricted to Supertest. If there are packages that can solve the problem that Supertest can't, they may be considered as well.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • what about `res.status(500).send()` – walkerrandophsmith Feb 23 '16 at 20:46
  • @WalkerRandolphSmith The headers are already sent by the first res.sendStatus, this will cause another 'Can't set headers after they are sent' error. – Estus Flask Feb 23 '16 at 21:54
  • Ahh I think I understand now. – walkerrandophsmith Feb 23 '16 at 22:03
  • I'm not getting the `Can't set headers after they are sent` error. – Soubhik Mondal Mar 01 '17 at 07:52
  • @BlazeSahlzen Thanks for noticing that, I forgot `next()` in the example above. – Estus Flask Mar 01 '17 at 08:14
  • @estus change the flow of application so that response should not be sent again after it has already been sent. – KlwntSingh Mar 03 '17 at 02:20
  • 1
    @KlwntSingh Sure. The question is how to detect this kind of problems in tests. You need to know about the problem to be able to fix it. – Estus Flask Mar 03 '17 at 02:28
  • @estus Hi, now I understood the problem. while using testing library, even if API have problem, the test will pass because API have returned the response, but server will stop. right ? – KlwntSingh Mar 04 '17 at 14:31
  • @KlwntSingh It's even worse, this won't stop the server. So the only way to figure out that something went wrong is to accidentally notice the error in server logs. Even if it won't fail the tests, it clearly means that some middlewares are in conflict. This is the scenario I'm trying to avoid. My goal is to fail the test if Express error handler gets an error (as it was said above, it can't just reply with 500, because headers were already sent). – Estus Flask Mar 04 '17 at 16:41
  • Perhaps using [response.finished](http://stackoverflow.com/questions/16254385/undocumented-response-finished-in-node-js) in the code may help? – Myonara Mar 05 '17 at 20:21
  • @Myonara Not sure how it could help here. Do you have some ideas? – Estus Flask Mar 06 '17 at 13:36
  • Well, when response.finished is working, the next/later router-routine can check this to avoid the error and not sending any information. – Myonara Mar 06 '17 at 16:11
  • @Myonara Thanks for the idea but I don't think that this will work. Adding this check to production code seems unreasonable, also send may happen in third-party middewares that cannot be modified. I would prefer to do this in tests if possible. – Estus Flask Mar 06 '17 at 16:44

3 Answers3

1

Try to just set the status code without send it and avoid send it twice throwing the error use res.status().

As the express documentation say it

Sets the HTTP status for the response. It is a chainable alias of Node’s response.statusCode.

IMHO if you want to detect it in a end-to-end (E2E) testing tool like supertest (or Selenium) you have to handle the express error and send a correct output (500 status error, some message...) to permit detect it.

Or use instead a unit test, to test that the controller function doesn't throw any error using chai or a native assertion.

Dario
  • 3,905
  • 2
  • 13
  • 27
  • And as a result, the response won't be sent. The point of the question is to detect the error, not to modify app code. – Estus Flask Mar 07 '17 at 18:32
  • Thanks. Regarding 500 error, the problem is still the same - once the 200 response has been sent, it cannot be changed to 500. And unit tests are supposed to be performed in isolation from other units. This means that the test may pass for particular middleware but will result in error when several middlewares are stacked. – Estus Flask Mar 08 '17 at 17:28
  • Yes, it broken the middleware chain and you just can check that the final ouput is not as you expect. – Dario Mar 08 '17 at 17:35
1

I answered a similar question here. The "can't set headers after they have been set" error should be raised by express as an unhandled exception. So, you should be able to get access to it via the unhandledException event that is raised by the process.

However, this is much more tricky because of the timing. Your test case's expect function and done function will be queued for processing on the event loop on the tick right after the first res.statusCode call. Unfortunately, the next call for res.statusCode can happen at an indeterminate amount of time afterwards. For example, what if the second route handler called a really slow webservice or db and then called res.statusCode.

With that in mind, your options are pretty hard. The brute force way is to wait in your test code for a determinate amount of time and then check. It's effective but slow and non-deterministic, which will cause your test to be flaky.

Another option is to check any instrumentation code that you might have in express. If you have code in express that keeps metrics of the number of in process calls for the various route handlers you could expose these metrics to your test code. Then one of your conditions for finishing your test is that all metrics for in process route calls are 0. The second option would allow you to write deterministic tests and be much faster because you could poll the metrics.

The final option would be to handle this test case through unit tests. This is probably the best solution because it would be deterministic and wouldn't require any sort of polling. However, the downside is that you need to know that both of your functions are called in order which leads you down a path of trying to recreate in your test code the logic that express uses to call route handlers.

Zambonilli
  • 4,358
  • 1
  • 18
  • 18
-2

I did this using HAPI instead of Express, but I solved the same problem. I used an external library to make the call (like request-promise) and it worked. Catch the error in the request-promise response.

Bradley
  • 2,071
  • 2
  • 21
  • 32
  • 1
    I'm not sure what you mean. In the example request-promise cannot catch the error, because there's no error in the response itself - it is 200. See the comments, this was already discussed there. – Estus Flask Mar 07 '17 at 09:52