cmorten / superoak

HTTP assertions for Oak made easy via SuperDeno. 🐿 🦕
https://cmorten.github.io/superoak/
MIT License
121 stars 8 forks source link

AssertionError: Test case is leaking async ops. #9

Closed antoniogamiz closed 4 years ago

antoniogamiz commented 4 years ago

Issue

Setup:

I am trying to run a simple test in my app, but it constantly fails because of leaking async ops, even though there's nothing running.

Details

import { Application} from "https://deno.land/x/oak@v6.3.1/mod.ts";
import { superoak } from "https://deno.land/x/superoak@2.3.1/mod.ts";

const app = new Application();
app.use((ctx) => {
  ctx.response.body = "Hello world!";
});

Deno.test("Example", async () => {
  const request = await superoak(app);
});

To run: deno test --allow-net <that code>.ts

Error log:

running 1 tests
test test ... FAILED (8ms)

failures:

test
AssertionError: Test case is leaking async ops.
Before:
  - dispatched: 0
  - completed: 0
After:
  - dispatched: 2
  - completed: 1

Make sure to await all promises returned from Deno APIs before
finishing test case.
    at assert (deno:cli/rt/06_util.js:33:13)
    at asyncOpSanitizer (deno:cli/rt/40_testing.js:44:7)
    at async Object.resourceSanitizer [as fn] (deno:cli/rt/40_testing.js:68:7)
    at async TestRunner.[Symbol.asyncIterator] (deno:cli/rt/40_testing.js:240:13)
    at async Object.runTests (deno:cli/rt/40_testing.js:317:22)

failures:

        test

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (8ms)
asos-craigmorten commented 4 years ago

Hey @antoniogamiz thanks for raising - not ideal at all given that really is the simplest usecase! 😅

Will try to spare some time soon to investigate - if you have time and are keen, please do have a dive into the code to figure out what async/promising thing is being left hanging.

It's odd this hasn't been picked up in the tests but haven't touched this in a month and may be that something in Oak / Deno has moved along since September... or just have been super lucky (or unlucky depending on how see it) with an async op race condition.

antoniogamiz commented 4 years ago

I know it's quite weird... At first, I thought it was due to some error in my files, but I have cleaned the cache and updated Deno to 1.4.6 and it's still happening.

I have downloaded this repo and I have run the tests but they are ok so...

I will try to investigate what's being left hanging.

asos-craigmorten commented 4 years ago

Haven't dug too deeply, but from a quick reminder of the code, performing:

const request = await superdeno(app);

Will launch an Oak server and resolve with a promise which we are awaiting. Given nothing then stops said Oak sever, my guesses are that it (the server) is effectively the async op that is not cleaned up (or more accurately the async iterator inside the Oak server that processes requests hasn't been escaped).

Hence I'm curious if the issue goes away if you actually complete the usage of superdeno as documented and make an action / assertion - superdeno has it's own internal logic to close the Oak server using an AbortController, and this will only be fired when the underlying superdeno .end() method is called, either implicitly or explicitly (REF: https://github.com/asos-craigmorten/superdeno/blob/main/src/test.ts#L350)

For instance, updating to the following solves it for me:

import { Application} from "https://deno.land/x/oak@v6.3.1/mod.ts";
import { superoak } from "https://deno.land/x/superoak@2.3.1/mod.ts";

const app = new Application();
app.use((ctx: any) => {
  ctx.response.body = "Hello world!";
});

Deno.test("Example", async () => {
  const request = await superoak(app);

  await request.get("/"); // Need to invoke at least one superdeno method otherwise the underlying server is left runner forever (well... until it gets cleaned up by Deno which will tell you off).
});
antoniogamiz commented 4 years ago

And why does this fail, then? It produces the same error:

import { Application } from "https://deno.land/x/oak@v6.3.1/mod.ts";
import { superoak } from "https://deno.land/x/superoak@2.3.1/mod.ts";

const app = new Application();
app.use((ctx: any) => {
  ctx.response.body = "Hello world!";
});

Deno.test("Example", async () => {
  const request = await superoak(app);

  await request.get("/").expect({});
});
antoniogamiz commented 4 years ago

I'm passing an object because my endpoint returns a JSON, so I want to test that.

antoniogamiz commented 4 years ago

Sorry ignore those two replies, what you have said works great. Thanks!