IT/DEV Study

[Nomad Corders] NestJS로 API 만들기 #4 정리 (완강)

Ella.J 2022. 7. 27. 16:22
728x90
반응형

2022.07.26 - [IT] - [Nomad Corders] NestJS로 API 만들기 #3 정리

 

[Nomad Corders] NestJS로 API 만들기 #3 정리

2022.07.25 - [IT] - [Nomad Corders] NestJS로 API 만들기 #2.4 to #2.7 정리 [Nomad Corders] NestJS로 API 만들기 #2.4 to #2.7 정리 2022.07.25 - [IT] - [Nomad Corders] NestJS로 API 만들기 #2.0 to #2.3..

ella-devblog.tistory.com


#4 E2E TESTING

 

#4.1 Testing movies

e2e 테스트는 movie 관련된 애플리케이션의 모든 부분을 테스트할 사용.

package.json

test\app.e2e-spec.ts

import { Test, TestingModule } from '@nestjs/testing';

import { INestApplication } from '@nestjs/common';

import * as request from 'supertest';

import { AppModule } from './../src/app.module';

describe('AppController (e2e)', () => {

  let app: INestApplication;

  beforeEach(async () => {

    const moduleFixture: TestingModule = await Test.createTestingModule({

      imports: [AppModule],

    }).compile();

    app = moduleFixture.createNestApplication();

    await app.init();

  });

  //root url test

  it('/ (GET)', () => {

    return request(app.getHttpServer())

      .get('/')

      .expect(200)

      .expect('Hello World!');

  });

});

 

[ROOT URL TEST]

웹사이트의 ROOT URL 요청을 보내고 'Hello World!' 나오는지 TEST.

  //root url test

  it('/ (GET)', () => {

    return request(app.getHttpServer())

      .get('/')

      .expect(200)

      .expect('Hello World!');

  });

 

[>num run test:e2e]

위의 e2e-spec 파일을 수정하지 않고 바로 테스트 해보면, 위처럼 FAIL .

왜냐하면 우리가 AppController에서 'Welcome to my Movie API'라고 나오게 수정했기 때문.

따라서, e2e-spec 파일을 .expect('Welcome to my Movie API'); 변경해주면 정상적으로 PASS됨.

 

e2e Testing에서는 Controller 있는 기능들을 테스트 해볼꺼임.

 

[GET TEST] : movies GET 요청 하는 것을 TEST.

app.getHttpServer() : app Server 주소 가져오기. http://localhost:3000/ 같은걸 쓰기 위해서.

  //movies get test

  it("/movies (GET)", () => {

    return request(app.getHttpServer())

      .get('/movies')

      .expect(200)

      .expect([]); //return value array?

  });

=> TEST PASS

 

  //movies get test

  it("/movies (GET)", () => {

    return request(app.getHttpServer())

      .get('/movies')

      .expect(200)

      .expect([{ id: 1 }]); //return value id:1?

  });

=> TEST FAIL

지금 movie data 없기때문에 테스트에 실패함.

 

다른 것도 테스트하기 위해 describe 이용하여 코드를 수정함.

  //movies url test

  describe('/movies', () => {

    it('GET', () => { //GET TEST

      return request(app.getHttpServer())

        .get('/movies')

        .expect(200)

        .expect([]);

    });

  });

여기서 it 구문을 추가해 테스트를 추가할 있음.

 

[POST 201 TEST] : 서버에 request해서 movies에 POST할 data 보내면 201 받는지 TEST.

  //movies url test

  describe('/movies', () => {

    it('POST 201', () => { //POST TEST

      return request(app.getHttpServer())

        .post('/movies')

        .send({

          title: 'Test',

          year: 2000,

          genres: ['test'],

        })

        .expect(201);

    });

  });

=> TEST PASS

 

[DELETE TEST] : DELETE endpoint까지 앱에 request해서 movies delete 요청하면 404 반환하는지 TEST.

  //movies url test

  describe('/movies', () => {

    it('DELETE', () => { //DELETE TEST

      return request(app.getHttpServer())

        .delete('/movies')

        .expect(404);

    })

  });

=> TEST PASS

 

[여기까지 코드]

  //movies url test

  describe('/movies', () => {

    it('GET', () => { //GET TEST

      return request(app.getHttpServer())

        .get('/movies')

        .expect(200)

        .expect([]);

    });

    it('POST', () => { //POST TEST

      return request(app.getHttpServer())

        .post('/movies')

        .send({

          title: 'Test',

          year: 2000,

          genres: ['test'],

        })

        .expect(201);

    });

    it('DELETE', () => { //DELETE TEST

      return request(app.getHttpServer())

        .delete('/movies')

        .expect(404);

    })

  });

728x90

#4.2 Testing GET movies id

이번에는 테스트하면서 생길법한 에러에 대해 원인이 무엇인지 알아보자!

 

NestJS 테스트마다 어플리케이션을 생성하고 있음. (실제 서버 어플리케이션이랑은 다름.)

이걸 이해하는 중요한 건지 아래에서 설명할꺼임.

  beforeEach(async () => {

    const moduleFixture: TestingModule = await Test.createTestingModule({

      imports: [AppModule],

    }).compile();

    app = moduleFixture.createNestApplication();

    await app.init();

  });

(참고) 근데 매번 테스트할 때마다 어플리케이션을 만들고 싶지 않고, POST 테스트할 만들었던 movie data 계속 기억하고 싶다. => 그러면, beforeEach beforeAll 바꿔주면 간단하게 해결!

 

게다가, jest에는 it.todo() 라는 아주 편리한 기능이 있음.

그대로 해야할 테스트 목록을 미리 정해놓는 것임.

실행해보면, 연필 모양으로 3가지 해야할 테스트가 있다고 알려줌.

나중에 이것을 실제 테스트 코드로 변경해주면 .

 

[GET 200 TEST]

여기서 GET 아래처럼 바꾸고 실행해보자.

  //movies/id url test

  describe('/movies/:id', () => {

    it('GET 200', () => {

      return request(app.getHttpServer())

        .get('/movies/1')

        .expect(200);

    });

    it.todo('DELETE');

    it.todo('PATCH');

  })

=> TEST FAIL!!

위에서 movie data 기억하도록 했기때문에,

id 1 movie GET 하면 정상적으로 가져오고 200 return 되야하는데,

실행해보면 결과값은 FAIL!!

? 이상하지?

그럼 getOne() console.log(typeof id); 추가해서 id 타입을 알아보자.

[>num run start:dev] 실제 서버를 작동해서 insomnia에서 id 타입을 확인하면 number 정상적임.

근데, [>num run test:e2e] 테스트 어플리케이션을 작동하면 타입이 string으로 나옴!! 이상하지?

 

이유는, 바로,,

main.ts에서 우리가 옵션으로 등록해줬던 transform:true 값이

실제 서버 작동시에 우리가 입력한 url:/movies/1 의 id 값이 자동으로 number 형변환되기 때문!

근데 e2e 테스트에서는 안 되는 이유가,,

pp.e2e-spec.ts 파일에서 어플리케이션을 생성해주지만, 어떤 pipe에도 올리지 않았음!!!

그.래.서 안 되는거임!!

그러니깐, e2e 테스트 유닛 테스트 점을 항상 기억하고,,,

★중요★ 테스팅 환경도 실제 구동 환경의 설정을 그대로 적용시켜줘야 한다! ★중요★

 

그럼 어떻게 바꿀까?

main.ts에서 pipe 생성하고, option 줬던 것을 e2e-spec 파일에 동일하게 넣어주면 완성!

 

  beforeAll(async () => {

    const moduleFixture: TestingModule = await Test.createTestingModule({

      imports: [AppModule],

    }).compile();

    app = moduleFixture.createNestApplication();

    //pipe 생성

    app.useGlobalPipes(new ValidationPipe({

      //options

      whitelist: true,

      forbidNonWhitelisted: true,

      //transform : 원래 param or body에서 받는 거는 string인데,

      //true로 하면 실제 원하는 값으로 변경해줌.

      transform: true,

    }));

    await app.init();

  });

그럼 실행결과 정상적으로 나옴!

 

[GET 404 TEST] : 200 정상 리턴에 추가로 404 에러까지 TEST

  //movies/id url test

  describe('/movies/:id', () => {

    //it.todo() : 말 그대로 해야할 테스트 목록

    it('GET 200', () => {

      return request(app.getHttpServer())

        .get('/movies/1')

        .expect(200);

    });

    it('GET 404', () => {

      return request(app.getHttpServer())

        .get('/movies/999')

        .expect(404);

    });

    it.todo('DELETE');

    it.todo('PATCH');

  })


#4.3 Testing PATCH and DELETE movies id

[PATCH, DELETE TEST] : 해당 id 가진 movies data PATCH, DELETE TEST

  //movies/id url test

  describe('/movies/:id', () => {

    it('PATCH 200', () => {

      return request(app.getHttpServer())

        .patch('/movies/1')

        .send({title:'Updated Test', year:2022, genres:['Updated Test']})

        .expect(200);

    });

    it('PATCH 404', () => {

      return request(app.getHttpServer())

        .patch('/movies/999')

        .send({title:'Updated Test', year:2022, genres:['Updated Test']})

        .expect(404);

    });

    it('DELETE 200', () => {

      return request(app.getHttpServer())

        .delete('/movies/1')

        .expect(200);

    });

    it('DELETE 404', () => {

      return request(app.getHttpServer())

        .delete('/movies/999')

        .expect(404);

    });

 

[POST 400 TEST] : 잘못된 data 가진 movie를 POST하는 TEST

  //movies url test

  describe('/movies', () => {

    it('POST 201', () => { //POST TEST

      return request(app.getHttpServer())

        .post('/movies')

        .send({

          title: 'Test',

          year: 2000,

          genres: ['test'],

        })

        .expect(201);

    });

    it('POST 400', () => { //POST TEST

      return request(app.getHttpServer())

        .post('/movies')

        .send({

          title: 'Test',

          year: 2000,

          genres: ['test'],

          other: 'thina'

        })

        .expect(400);

    });

  });

반응형

[최종 app.e2e-spec.ts 코드]

import { Test, TestingModule } from '@nestjs/testing';

import { INestApplication, ValidationPipe } from '@nestjs/common';

import * as request from 'supertest';

import { AppModule } from './../src/app.module';

describe('AppController (e2e)', () => {

  let app: INestApplication;

  beforeAll(async () => {

    const moduleFixture: TestingModule = await Test.createTestingModule({

      imports: [AppModule],

    }).compile();

    app = moduleFixture.createNestApplication();

    //pipe 생성

    app.useGlobalPipes(new ValidationPipe({

      //options

      whitelist: true,

      forbidNonWhitelisted: true,

      //transform : 원래 param or body에서 받는 거는 string인데,

      //true로 하면 실제 원하는 값으로 변경해줌.

      transform: true,

    }));

    await app.init();

  });

  //root url test

  it('/ (GET)', () => {

    return request(app.getHttpServer())

      .get('/')

      .expect(200)

      .expect('Welcome to my Movie API');

  });

  //movies url test

  describe('/movies', () => {

    it('GET', () => { //GET TEST

      return request(app.getHttpServer())

        .get('/movies')

        .expect(200)

        .expect([]);

    });

    it('POST 201', () => { //POST TEST

      return request(app.getHttpServer())

        .post('/movies')

        .send({

          title: 'Test',

          year: 2000,

          genres: ['test'],

        })

        .expect(201);

    });

    it('POST 400', () => { //POST TEST

      return request(app.getHttpServer())

        .post('/movies')

        .send({

          title: 'Test',

          year: 2000,

          genres: ['test'],

          other: 'thina'

        })

        .expect(400);

    });

    it('DELETE', () => { //DELETE TEST

      return request(app.getHttpServer())

        .delete('/movies')

        .expect(404);

    })

  });

  //movies/id url test

  describe('/movies/:id', () => {

    //it.todo() : 말 그대로 해야할 테스트 목록

    it('GET 200', () => {

      return request(app.getHttpServer())

        .get('/movies/1')

        .expect(200);

    });

    it('GET 404', () => {

      return request(app.getHttpServer())

        .get('/movies/999')

        .expect(404);

    });

    it('PATCH 200', () => {

      return request(app.getHttpServer())

        .patch('/movies/1')

        .send({title:'Updated Test', year:2022, genres:['Updated Test']})

        .expect(200);

    });

    it('PATCH 404', () => {

      return request(app.getHttpServer())

        .patch('/movies/999')

        .send({title:'Updated Test', year:2022, genres:['Updated Test']})

        .expect(404);

    });

    it('DELETE 200', () => {

      return request(app.getHttpServer())

        .delete('/movies/1')

        .expect(200);

    });

    it('DELETE 404', () => {

      return request(app.getHttpServer())

        .delete('/movies/999')

        .expect(404);

    });

  })

});

test:e2e 결과 화면.


(참고) HTTP 상태 코드

https://www.whatap.io/ko/blog/40/

 

HTTP 상태 코드 정리 | 와탭 블로그

HTTP 응답 상태 코드의 목록을 정리하여 소개합니다. 클라이언트의 요청에 따라 어떻게 서버가 응답하는지 알아봅시다.

www.whatap.io

 

728x90
반응형