0

In the following code block (example #1), expect(this.req.headers["user-agent"]).to.equal("BOOM") throws error and test fails.

describe("http.get with headers", () => {
  it("should return response with status code 200", async () => {
    const userAgent =
      "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:74.0) Gecko/20100101 Firefox/74.0"
    nock("https://api.example.net")
      .get("/hello")
      .reply(function() {
        expect(this.req.headers["user-agent"]).to.equal("BOOM")
        return [200]
      })
    const httpInstance = new http({
      headers: {
        "user-agent": userAgent,
      },
    })
    let response = await httpInstance.get("https://api.example.net/hello")
    expect(response.statusCode).to.equal(200)
  })
})

In the following code block (example #2), expect(requestBody.message).to.equal("BOOM") throws "silent" error (return [200] is never executed), but the test passes. Why?

describe("logger.captureMessage(message, callback)", () => {
  it("should send captured exception to sentry", () => {
    return new Promise((resolve, reject) => {
      nock("https://sentry.io")
        .post("/api/3926156/store/")
        .reply((uri, requestBody: any) => {
          expect(requestBody.message).to.equal("BOOM")
          return [200]
        })
      logger.captureMessage("foo", () => {
        resolve()
      })
    })
  })
})

Using catch and triggering reject works, but why is this necessary when everything works fine in example #1?

describe("logger.captureMessage(message, callback)", () => {
  it("should send captured exception to sentry", () => {
    return new Promise((resolve, reject) => {
      nock("https://sentry.io")
        .post("/api/3926156/store/")
        .reply((uri, requestBody: any) => {
          try {
            expect(requestBody.message).to.equal("BOOM")
            return [200]
          } catch (error) {
            reject(error)
          }
        })
      logger.captureMessage("foo", () => {
        resolve()
      })
    })
  })
})
sunknudsen
  • 6,356
  • 3
  • 39
  • 76
  • 1. you didn't provide the error message. 2. guess: you're trying to access an attribute that doesn't exist (like req or headers) here: `this.req.headers["user-agent"]` – Nir Alfasi Mar 12 '20 at 13:44
  • @alfasin Thanks for your help. No error message. The expect assertion simply doesn’t throw error so the test passes even though it shouldn’t. – sunknudsen Mar 12 '20 at 13:46
  • your code says: "This expect throws error and stops lab." so now I'm confused... Is it an error without an error message??? – Nir Alfasi Mar 12 '20 at 13:47
  • See https://hapi.dev/family/code/api/?v=8.0.1#expectvalue-prefix and https://hapi.dev/family/code/api/?v=8.0.1#equalvalue-options – sunknudsen Mar 12 '20 at 13:49
  • @alfasin The question is about why one expect assertion makes the test fail and the other doesn’t when the code is very similar. Puzzled. – sunknudsen Mar 12 '20 at 13:54
  • did you try to put a breakpoint on the except that fails and inspect? good chances are that `requestBody` returns null/undefined and the code fails when you try to access `requestBody.message` – Nir Alfasi Mar 12 '20 at 20:39
  • Good suggestion @alfasin, but I confirm `requestBody` is set. Matt is probably right but I can’t find official docs to confirm his theory. – sunknudsen Mar 12 '20 at 20:42

1 Answers1

1

Making assertions inside Nock reply callbacks is not recommended. There are a lot of layers between where that function gets executed and your test runner. Most notably, the client making the request. Usually clients don't handle non-request errors well and end up concealing or even glossing over the error.

There isn't one "correct" way to do this. By the looks of your second test, I'd recommend using a test level variable to store the body of the request. Then do the asserting in the captureMessage callback.

Another note is that you're not correctly handing bubbling errors in the Promise of your second test. This line: return new Promise((resolve, reject) => { doesn't use an async keyword, which means you must call reject manually or any bubbling error will be lost in the ethos.

Matt R. Wilson
  • 7,268
  • 5
  • 32
  • 48
  • Thanks for helping out Matt. Do you know where I can read more about "another note"? Do you mean that using `async` and `await` somehow handles these bubbling errors? If so, why? How? I noticed that wrapping `expect(requestBody.level).to.equal("BOOM")` in a `try` `catch` and calling `reject` from withing the `catch` worked, but I can’t wrap my mind around why. – sunknudsen Mar 12 '20 at 17:08
  • Btw, storing `requestBody` for analyses inside of the callback of `captureMessage` still doesn’t throw an error, but since `resolve()` is not executed, the test fails because of a timeout error. – sunknudsen Mar 12 '20 at 17:17
  • I'm fairly confident that my "another note" is the actual answer to your posted question. But the asserting in a `reply` is gotcha that gets a lot of folks. As for how async/await works with errors, I don't have a great teaching resource off hand, so I recommend searching around. The gist is that async/await is syntactic sugar around manual Promises _with error handling_. When manually constructing Promises, you are required to also manually handle errors. – Matt R. Wilson Mar 12 '20 at 19:08