본문 바로가기
개발자 도전기/[STUDY] TEST CODE

TDD | node.js | 사용자 조회, 삭제, 추가, 수정 API 테스트

by 답수 2022. 5. 24.
728x90
SMALL

 

 

※ 인프런 - 테스트주도개발(TDD)로 만드는 NodeJS API 서버 강의를 기반으로 정리한 내용입니다.

 

 

사용자 조회 API 테스트 코드

사용자를 조회하는 API는 /users/:id로 설정한다.

 

성공 시

  • id가 1인 유저 객체를 반환

실패 시

  • id가 숫자가 아닐 경우 400으로 응답
  • id로 유저를 찾을 수 없을 경우 404 응답

성공 시 테스트 코드를 먼저 작성해보자.

// index.spec.js


describe('GET /users/:id는', () => {
    describe('성공 시', () => {
        it('id가 1인 유저 객체를 반환한다', (done) => {
            request(app)
                .get('/users/1')
                .end((err, res) => {
                    res.body.should.have.property('id', 1);     // id 프로퍼티의 값이 1일 때
                    done();
                });
        });
    });
});

 

애플리케이션 코드는 다음과 같다.

// index.js


app.get('/users/:id', function(req, res) {
    const id = parseInt(req.params.id, 10);
    const user = users.filter((user) => user.id === id)[0];
    res.json(user);
});

 

다음은 실패 시 테스트 코드

    describe('실패 시', () => {
        it('id가 숫자가 아닐 경우 400으로 응답한다', (done) => {
            request(app)
                .get('/users/one')
                .expect(400)
                .end(done);
            });
            
        it('id로 유저를 찾을 수 없을 경우 404로 응답한다', (done) => {
            request(app)
                .get('/users/999')
                .expect(404)
                .end(done);
        });
    });

 

애플리케이션 코드 수정

app.get('/users/:id', function(req, res) {
    const id = parseInt(req.params.id, 10);
    if (Number.isNaN(id)) return res.status(400).end();
    const user = users.filter((user) => user.id === id)[0];
    if (!user) return res.status(404).end();
    res.json(user);
});

 

결과는 통과

 

 

사용자 삭제 API 테스트 코드

사용자 삭제 API는 DELETE 메소드를 이용하여 /users/:id 로 해당 사용자를 삭제한다.

성공 시

  • 204 응답

실패 시

  • id가 숫자가 아닐 경우 400으로 응답

테스트 코드

describe('DELETE /users/:id는', () => {
    describe('성공 시', () => {
        it('204를 응답한다', (done) => {
            request(app)
                .delete('/users/1')
                .expect(204)
                .end(done);
        });
    });

    describe('실패 시', () => {
        it('404를 응답한다', (done) => {
            request(app)
                .delete('/users/one')
                .expect(400)
                .end(done);
        });
    });
});

 

애플리케이션 코드

app.delete('/users/:id', (req, res) => {
    const id = parseInt(req.params.id, 10);
    if (Number.isNaN(id)) return res.status(400).end();
    users = users.filter(user => user.id !== id);
    res.status(204).end();
});

 

 

사용자 추가 API 테스트 코드

사용자 추가는 POST 메소드를 이용할 것이다.

성공 시

  • 201 상태코드 반환
  • 생성된 유저 객체를 반환
  • 입력한 name을 반환

실패 시

  • name 파라미터 누락 시 400 응답
  • name이 중복일 경우 409 응답

테스트 코드는 다음과 같이 입력했다.

describe('POST /users', () => {
    describe('성공 시', (done) => {
        let body;
        // before(): 테스트케이스가 동작하기 전에 미리 실행되는 함수. 코드의 중복성 없애는데 효율적
        before(done => {
            request(app)
                .post('/users')
                .send({name: '노홍철'})
                .expect(201)
                .end((err, res) => {
                    body = res.body;
                    done();
                });
        })
        
        it('생성된 유저 객체를 반환한다', () => {
            body.should.have.property('id');
        });

        it('입력한 name을 반환한다', () => {
            body.should.have.property('name', '노홍철');
        });
    });

    describe('실패 시', () => {
        it('name 파라미터 누락 시 400을 반환한다', (done) => {
            request(app)
                .post('/users')
                .send({})
                .expect(400)
                .end(done);
        });
        
        it('name이 중복일 경우 409를 반환한다', (done) => {
            request(app)
                .post('/users')
                .send({name: '노홍철'})
                .expect(409)
                .end(done);
        });
    });
});

여기서 before()는 mocha의 메소드 중 하나로, it()을 통해 실제 테스트를 진행하기 이전에 먼저 실행해야 하는 코드를 실행시키는 함수다. 유저 객체를 반환하는 테스트와 입력한 name을 반환하는 함수는 이미 실행된 코드에서 body 내의 프로퍼티만 다른 것을 사용하면 되기 때문에 저렇게 중복을 피해서 작성했다.

 

다음은 애플리케이션 코드

app.post('/users', (req, res) => {
    const name = req.body.name;     // express는 body 프로퍼티 지원하지 않기 때문에 body-parser 모듈 필요
    if (!name) return res.status(400).end();
    const isConflict = users.filter(user => user.name === name).length;
    if (isConflict) return res.status(409).end();
    const id = Date.now();          // id는 고유한 값으로 설정해야 함. 임시로 현재 시간으로
    const user = {id, name};
    users.push(user);
    res.status(201).json(user);
});

 

node.js의 express는 body 프로퍼티를 지원하지 않기 때문에 body-parser 모듈이 필요하다. 즉 이 모듈이 있어야 요청 데이터의 body로부터 파라미터를 편리하게 추출할 수 있다. 만약 body-parser를 사용하지 않으면 body의 디폴트값이 undefined로 설정된다.

const bodyParser = require('body-parser');

...

app.use(bodyParser.json());     // for parsing application/json
app.use(bodyParser.urlencoded({ extended: true }));     // for parsing application/x-www-form-urlencoded

 

라고 강의를 듣고 구글링으로 더 찾아보고 있는데 express 4.16.0 버전부터는 body-parser가 express에 내장되어 있기 때문에 따로 설치하지 않아도 된다고 하고, 미들웨어는 다음과 같이 설정하면 된다.

app.use(express.json());     // for parsing application/json
app.use(express.urlencoded({ extended: true }));     // for parsing application/x-www-form-urlencoded

express.json()은 json으로 이루어진 Request Body를 파싱하기 위해서 데이터를 json형식으로 분석하기 위해 필요한 미들웨어다. 이 미들웨어만 작성해도 파싱이 가능하다.

 

아래의 urlencoded({extended: })는 무엇일까?

만약 extended 옵션이 false로 설정되어 있다면 node.js의 기본 내장인 queryString으로 파싱하고, true라면 npm의 qs 라이브러리를 사용한다고 한다. 둘 다 url의 쿼리스트링을 파싱하는 역할을 하지만, qs같은 경우 추가적인 보안 기능도 갖춘 확장 형태라고 한다.

 

 

사용자 수정 API 테스트 코드

마지막 API로 사용자 name를 수정하는 API를 만들 것이다. PUT /users/:id 로 작동한다.

성공 시

  • 변경된 정보를 응답한다

실패 시

  • 정수가 아닌 id일 경우 400 응답
  • name이 없을 경우 400 응답
  • 없는 유저일 경우 404 응답
  • 이름이 중복일 경우 409 응답

테스트 코드

describe('PUt /users/:id', () => {
    describe('성공 시', () => {
        it('변경된 name을 응답한다', (done) => {
            request(app)
                .put('/users/4')
                .send({name: '정형돈'})
                .end((err, res) => {
                    res.body.should.have.property('name', '정형돈');
                    done();
                });
            });
    });

    describe('실패 시', () => {
        it('정수가 아닌 id일 경우 400 응답', (done) => {
            request(app)
                .put('/users/four')
                .expect(400)
                .end(done);
        });

        it('name이 없을 경우 400 응답', (done) => {
            request(app)
                .put('/users/2')
                .send({})
                .expect(400)
                .end(done);
        });

        it('없는 유저일 경우 404 응답', (done) => {
            request(app)
                .put('/users/999')
                .send({name: 'foo'})
                .expect(404)
                .end(done);
        });

        it('이름이 중복일 경우 409 응답', (done) => {
            request(app)
                .put('/users/2')
                .send({name: '정준하'})
                .expect(409)
                .end(done);
        });
    });
});

 

애플리케이션 코드

app.put('/users/:id', (req, res) => {
    const id = parseInt(req.params.id);
    if (Number.isNaN(id)) return res.status(400).end();

    const name = req.body.name;
    if (!name) return res.status(400).end();

    const isConflict = users.filter(user => user.name === name).length;
    if (isConflict) return res.status(409).end();

    const user = users.filter(user => user.id === id)[0];
    if (!user) return res.status(404).end();
    
    user.name = name;
    res.json(user);
});

 

총 결과

> tdd-node@1.0.0 test
> mocha index.spec.js

Example app listening on port 3000!


  GET /users는
    성공 시
GET /users 200 3.023 ms - 110
      ✔ 유저 객체를 담은 배열로 응답한다
GET /users?limit=2 200 0.402 ms - 57
      ✔ 최대 limit 갯수만큼 응답한다
    실패 시
GET /users?limit=two 400 0.150 ms - -
      ✔ limit이 숫자형이 아니면 상태 코드 400 응답

  GET /users/:id는
    성공 시
GET /users/1 200 0.881 ms - 27
      ✔ id가 1인 유저 객체를 반환한다
    실패 시
GET /users/one 400 0.189 ms - -
      ✔ id가 숫자가 아닐 경우 400으로 응답한다
GET /users/999 404 0.092 ms - -
      ✔ id로 유저를 찾을 수 없을 경우 404로 응답한다

  DELETE /users/:id는
    성공 시
DELETE /users/1 204 0.276 ms - -
      ✔ 204를 응답한다
    실패 시
DELETE /users/one 400 0.166 ms - -
      ✔ 404를 응답한다

  POST /users
    성공 시
POST /users 201 21.831 ms - 39
      ✔ 생성된 유저 객체를 반환한다
      ✔ 입력한 name을 반환한다
    실패 시
POST /users 400 1.237 ms - -
      ✔ name 파라미터 누락 시 400을 반환한다
POST /users 409 0.932 ms - -
      ✔ name이 중복일 경우 409를 반환한다

  PUt /users/:id
    성공 시
PUT /users/4 200 0.729 ms - 27
      ✔ 변경된 name을 응답한다
    실패 시
PUT /users/four 400 0.124 ms - -
      ✔ 정수가 아닌 id일 경우 400 응답
PUT /users/2 400 0.293 ms - -
      ✔ name이 없을 경우 400 응답
PUT /users/999 404 0.410 ms - -
      ✔ 없는 유저일 경우 404 응답
PUT /users/2 409 0.291 ms - -
      ✔ 이름이 중복일 경우 409 응답


  17 passing (196ms)
728x90
LIST

댓글