Skip to main content

Command Palette

Search for a command to run...

[TIL] Integration Test using Supertest

05/10/23

Published
[TIL] Integration Test using Supertest

๐Ÿ“Œ Issues encountered

Issue #1

Using supertest to test login and auth middleware.

Issue #2

Creating test cases for unauthorized access to modify or delete resources.

Issue #3

Refactoring related to Issues #1 and #2.

๐Ÿ“Œ What I tried

Issue #1

  1. Define an object to hold userData.

  2. Login API test:

test("POST /api/auth/login API (login) Integration Test", async () => {
  const loginRequestBodyParams = {
    nickname: userData.nickname,
    password: userData.password,
  };
  const response = await supertest(app)
    .post(`/api/auth/login`) // API's HTTP Method & URL
    .send(loginRequestBodyParams); // Request Body

  /** Validation logic for POST /api/auth/login API **/
  // 1. If the login is successful, it should return a 200 HTTP status code.
  expect(response.status).toEqual(200);
  // 2. After the API call is completed, it should pass the Authorization and refreshtoken values as body data.
  userData.accessToken = response.body.Authorization;
  userData.refreshToken = response.body.refreshtoken;
});

The login API test stores the access token and refresh token in the userData object. 3) Mocking the auth middleware:

test("POST /api/worldcup API (createWorldcup) Integration Test Error Case, Invalid Params Error", async () => {
  const createWorldcupRequestBodyParamsByInvalidParamsError = {
    title: "Title_InvalidParamsError",
    content: "Content_InvalidParamsError",
  };

  const response = await supertest(app)
    .post(`/api/worldcup`) // API's HTTP Method & URL
    .set("Authorization", `Bearer ${userData.accessToken}`) // Set Access Token in the header
    .set("refreshtoken", `${userData.refreshToken}`) // Set Refresh Token in the header
    .send(createWorldcupRequestBodyParamsByInvalidParamsError); // Request Body

  /** Validation logic for POST /api/worldcup API error case **/
  // 1. If the InvalidParamsError occurs during the API call, it should return a 412 HTTP status code.
  expect(response.status).toEqual(412);
  // 2. The API response should have the format { errorMessage: '"choices" is required' }.
  expect(response.body).toMatchObject({
    errorMessage: '"choices" is required',
  });
});
  • The set() function is used to set data in the header. Since the token information is included in the header during login, the auth middleware expects to retrieve both tokens from the header.

  • The above code mocks the process of retrieving the two tokens from the header in the auth middleware.

Issue #2

  1. I signed up and logged in with a different user.

  2. Initially, I performed delete with user1, but there were no worldcup post data to compare, so I modified the logic to have user1 create two worldcups instead.

  3. Is there a better way to approach this?

Issue #3

  1. supertest allows the use of an agent, a virtual user!

  2. In particular, when I initially attempted integration testing without an agent, I got stuck in the part where the auth middleware is processed. However, supertest provides a method called agent() that behaves similarly to a browser sending cookie values by default.

  3. In other words, an agent can maintain the login state by persistently sending requests!

  4. The usage of agent() is as follows:

describe('POST /api/auth/login', () => {
  const agent = request

.agent(app);
  beforeEach((done) => {
    agent.post('/api/auth/login')
         .send({
           nickname: userData.nickname,
           password: userData.password,
           email: userData.email,
         })
         .end(done);
  });

  test("POST /api/worldcup", async () => {
    const createWorldcupRequestBodyParams = {
      title: worldcupData2.title,
      content: worldcupData2.content,
      choices: worldcupData2.choices,
    };
    const response = await agent
      .post(`/api/worldcup`)
      .send(createWorldcupRequestBodyParams);

    expect(response.status).toEqual(201)
    expect(response.body).toMatchObject({
      newWorldcup: {
        worldcup_id: worldcupData2.worldcup_id,
        user_id: userData.user_id,
        title: createWorldcupRequestBodyParams.title,
        content: createWorldcupRequestBodyParams.content,
        choices: createWorldcupRequestBodyParams.choices,
      },
    });
  });
});

๐Ÿ“Œ What I newly learned

  • Integration testing with supertest

  • Test-Driven Development (TDD)

  • Nodemailer

  • Design pattern - Creational pattern

๐Ÿ“Œ What to learn next

  • CI/CD

  • Troubleshooting presentation section summary

  • Storytelling presentation summary

More from this blog

[์ฝ”ํ…Œ] ๊ทธ๋ฆฌ๋”” ๋ฌธ์ œ - ๋ฌด์ง€์˜ ๋จน๋ฐฉ ๋ผ์ด๋ธŒ

https://school.programmers.co.kr/learn/courses/30/lessons/42891 ํšจ์œจ์„ฑ ํ…Œ์ŠคํŠธ์— ์‹ ๊ฒฝ์จ์•ผ ํ•˜๋Š” ๋ฌธ์ œ ์šฐ์„ ์ˆœ์œ„ ํ๋ฅผ ํ™œ์šฉํ•ด์„œ ๋จน๋Š” ์‹œ๊ฐ„์ด ์งง์€ ์Œ์‹๋ถ€ํ„ฐ ํ์—์„œ ๋นผ๊ธฐ import heapq # ์šฐ์„ ์ˆœ์œ„ํ ํ™œ์šฉ: food_time์ด ์งง์€ ์Œ์‹๋ถ€ํ„ฐ ์‚ญ์ œ def solution(food_times, k): if sum(food_times) <= k: return -1 ...

Apr 4, 2024
[์ฝ”ํ…Œ] ๊ทธ๋ฆฌ๋”” ๋ฌธ์ œ - ๋ฌด์ง€์˜ ๋จน๋ฐฉ ๋ผ์ด๋ธŒ

[์ฝ”ํ…Œ] ์—ฌํ–‰๊ฒฝ๋กœ

๐Ÿ’ก [์ถœ๋ฐœ์ง€, ๋„์ฐฉ์ง€] ํ˜•ํƒœ๋กœ ์ฃผ์–ด์ง„ ๋น„ํ–‰๊ธฐ ํ‹ฐ์ผ“์„ ํ†ตํ•ด ๋ชจ๋“  ํ‹ฐ์ผ“์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ์˜ ๊ณตํ•ญ์„ ๋ฐฉ๋ฌธ ์ˆœ์„œ ๊ตฌํ•˜๊ธฐ (๋‹จ, ์—ฌ๋Ÿฌ ๊ณตํ•ญ์„ ๋ฐฉ๋ฌธํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ ์•ŒํŒŒ๋ฒณ์ด ๋น ๋ฅธ ๊ณตํ•ญ๋ถ€ํ„ฐ ๋ฐฉ๋ฌธํ•œ๋‹ค.) ํ‹€๋ ธ๋˜ ์ฝ”๋“œ from collections import defaultdict def dfs(graph, route, depart): if graph[depart]: connected = graph[depart][0] ...

Feb 26, 2024
[์ฝ”ํ…Œ] ์—ฌํ–‰๊ฒฝ๋กœ

siwon.log

161 posts