javascriptexpressjestjssupertest

Extend call stack of failed test outside of helper function, up to the test case


While writing unit tests for my codebase, I've noticed that I'm duplicating the code that does the HTTP request and adds expectations for the response. So I ended up with some helper functions, one of those is called: expectedResponseForGET, which I made static inside a TestUtils class, in a file called: utils.ts.

In some tests I call that method multiple times to check multiple things after a POST. But when that expectation fails (the response is not what I expected to be), the test fails and the call stack shows that it failed the expectation from the expectedResponseForGET function, without the call stack to the test which called that helper function.

The call stack I receive for the failed expectation looks like this:

src/tests/sites/add.test.ts
  Add site route
    ✓  User4 - Add site, not company manager (238 ms)
    ✕  User1 - Add site, company manager (269 ms)

  ● Add site route ›  User1 - Add site, company manager

    expect(received).toEqual(expected) // deep equality

    - Expected  -  1
    + Received  +  1

    @@ -1,63 +1,10 @@
    -       "location": "Location2",
    +       "location": "Location",

      92 |       .set('Authorization', user.token);
      93 |     expect(res.status).toBe(200);
    > 94 |     expect(res.body).toEqual(response);
         |                      ^
      95 |     return res;
      96 |   };
      97 |

      at src/tests/utils.ts:94:22
      at fulfilled (src/tests/utils.ts:5:58)

This part:

at src/tests/utils.ts:94:22            -> This is for the line containing the expectation
at fulfilled (src/tests/utils.ts:5:58) -> This is for the import of request from supertest

Which doesn't contain the line from src/tests/sites/add.test.ts test file which called expectedResponseForGET.

Here is the test:

test(' User1 - Add site, company manager', async () => {
    await TestUtils.expectedResponseForPOST(
      server,
      C1.users.U1,
      route,
      C1.sites.S5.requests.addSite,
      C1.sites.S5.responses.fullSite,
    );

    await TestUtils.expectedResponseForGET(
      server,
      C1.users.U1,
      routeSites,
      C1.sites.responses.allAfterS5,
    );

   await TestUtils.expectedResponseForGET(
      server,
      C1.users.U1,
      routeWork,
      C1.work.responses.allAfterS5,
    );
  });

Here are the helper functions:

import request from 'supertest';

class TestUtils{
  static expectedResponseForGET = async (
    server: Express,
    user: UserTest,
    route: string,
    response: GenericResponse,
  ) => {
    const res = await request(server)
      .get(route)
      .set('Authorization', user.token);
    expect(res.status).toBe(200);
    expect(res.body).toEqual(response);
    return res;
  };

  static expectedResponseForPOST = async (
    server: Express,
    user: UserTest,
    route: string,
    // eslint-disable-next-line @typescript-eslint/ban-types
    req: Object,
    response: GenericResponse,
  ) => {
    const res = await request(server)
      .post(route)
      .send(req)
      .set('Authorization', user.token);
    expect(res.status).toBe(200);
    expect(res.body).toEqual(response);
    return res;
  };
}

Has anyone encountered this problem and knows a fix for this or a workaround to display the line from the test which called the helper method?


Solution

  • I managed to find a solution for this, by saving the stack in that utils method and appending it to the error that is thrown when the test fails:

    static expectedResponseForPOST = async (
        server: Express,
        user: UserTest,
        route: string,
        // eslint-disable-next-line @typescript-eslint/ban-types
        req: Object,
        response: GenericResponse,
      ) => {
        const originalStack = new Error().stack;
    
        try {
        const res = await request(server)
          .post(route)
          .send(req)
          .set('Authorization', user.token);
        expect(res.status).toBe(200);
        expect(res.body).toEqual(response);
        return res;
        }catch (err) {
          if (err instanceof Error) {
            err.stack = `${err.stack}\nComplete Stack:\n${originalStack}`;
          }
          throw err;
        }
    
      };
    

    This will result in logging like this:

    at src/tests/utils.ts:140:26
          at fulfilled (src/tests/utils.ts:5:58)
          Complete Stack:
          at src/tests/utils.ts:131:27
          at src/tests/utils.ts:8:71
          at Object.<anonymous>.__awaiter (src/tests/utils.ts:4:12)
          at Function.Object.<anonymous>.TestUtils.expectedResponseForPOST (src/tests/utils.ts:118:7)
          at Function.<anonymous> (src/tests/data/models/WarehouseEventTest.ts:39:21)
          at src/tests/data/models/WarehouseEventTest.ts:8:71
          at Object.<anonymous>.__awaiter (src/tests/data/models/WarehouseEventTest.ts:4:12)
          at Function.checkIncrementalEventsAfterAction (src/tests/data/models/WarehouseEventTest.ts:24:16)
          at Function.<anonymous> (src/tests/data/models/WarehouseEventTest.ts:20:30)
          at src/tests/data/models/WarehouseEventTest.ts:8:71
          at Object.<anonymous>.__awaiter (src/tests/data/models/WarehouseEventTest.ts:4:12)
          at Function.checkIncrementalEventAfterAction (src/tests/data/models/WarehouseEventTest.ts:18:16)
          at src/tests/warehouse/warehouseItem.test.ts:166:30
          at fulfilled (src/tests/warehouse/warehouseItem.test.ts:5:58)
    

    Which is the complete stack