본문 바로가기
학습/Node.js

TDD로 API 서버 개발하기

코동이 2022. 1. 15.

* 개요

 

앞 글에서, mocha, should 사용법을 알아보았습니다. 이제, 실제 API 서버를 어떻게 TDD로 개발할 수 있는지 실습합니다. HTTP 메서드인 GET, POST, PUT, DELTE 4가지를 실습하고 예외 상황도 검사합니다.

 

1. GET /users

 

- 성공시 유저객체를 담은 배열로 응답한다

- 성공시 최대 limit 갯수만큼 응답한다.

- 실패시 limit이 숫자형이 아니면 400을 응답한다

 

//index.spec.js
const request = require("supertest");
const should = require("should");
const app = request("./index");

describe('GET /users 는', () => {
  describe('성공시', () => {
    it('유저 객체를 담은 배열로 응답한다', (done) => {
     request(app)
     	.get('/users')
        .end((err,res) => {
          res.body.should.be.instanceOf(Array);
          done();
        });
    });
    
    it('최대 limit 갯수만큼 응답한다', (done) => {
      request(app)
      	.get("/users?limit=2")
        .end((err, res) => {
          res.body.should.have.lengthOf(2);
          done();
        });
    });
  };
  
  describe('실패시', () => {
    it('limit이 숫자형이 아니면 400을 응답한다', (done) => {
    	request(app)
        	.get("/users?limit=two")
            .expect(400)
            .end(done);
    });
  });
};

 

//index.js

const express = require("express");
const app = express();
const morgan = require("morgan");

let users = [
    {id: 1, name: 'alice'},
    {id: 2, name: 'bak'},
    {id: 3, name: 'chris'},
];

app.use(morgan('dev'));

app.get("/", function(req, res) {
	res.json(user);
});

app.get("/users", function(req, res) {
    req.query.limit = req.query.limit || 10;
    const limit = parseInt(req.query.limit, 10);
    
    if(Number.isNaN(limit)) {
      return res.status(400).end();
    }
    
    return res.json(users.slice(0, limit));
});

 

2. GET /users/:id

 

- 성공시 id가 1인 유저 객체를 반환한다

- 실패시 id가 숫자가 아닐경우 400을 응답한다

- 실패시 id로 유저를 찾을 수 없는 경우 404를 응답한다

 

//index.spec.js

describe('GET /users/1', () => {
    describe('성공시', () => {
        it('id가 1인 유저 객체를 반환한다', done => {
            request(app)
            .get('/users/1')
            .end((err, res) => {
                res.body.should.have.property('id', 1);
                done();
            });
        });
    });
    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);

        });
    });
});

 

 

//index.js

app.get("/users/:id", function(req, res) {
  const id = parseInt(req.query.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();
  
  return res.json(user);
});

 

3. POST /users

 

- 성공시 생성된 유저 객체를 반환한다

- 성공시 입력한 name을 반환한다

- 실패시 name 파라미터 누락시 400을 반환한다

- 실패시 name이 중복일 경우 409를 반환한다

 

describe("POST /users", () => {
  describe("성공시", () => {
    let name = 'daniel';
    let body;
    before(done => {
      request(app)
      	.post("/users")
        .send({name})
        .expect(201)
        .end((err, res) => {
          body = res.body;
          done();
        });
    });
  });
  
  it("생성된 유저 객체를 반환한다", () => {
    body.should.have.propety('id');
  });
  it("입력한 name을 반환한다", () => {
    body.should.have.propety('name', name);
  });
  
  describe("실패시", () => {
    it('name 파라미터 누락시 400을 반환한다', (done) => {
      request(app)
      	.post("/users")
        .send({})
        .expect(400)
        .end(done);
    });
    
    it('name이 중복일 경우 409를 반환한다', (done) => {
      request(app)
      	.post("/users")
        .send({})
        .expect(400)
        .end(done);
    });
  });
});

 

//index.js

app.post("/users", function(req, res) {
  const name = req.body.name;
  if(!name) return res.status(400).end();
  
  const isConflicted = users.filter(user => user.name === name).length;
  if(isConflicted) return res.status(409).end();
  
  const id = Date.now();
  const user = {id, name};
  users.push(user);
  return res.status(201).json(user);
});

 

4. PUT /:id

 

- 성공시 변경된 name을 응답한다

- 실패시 정수가 아닌 id일 경우 400을 응답한다

- 실패시 name이 없을 경우 400을 응답한다

- 실패시 없는 유저일 경우 404를 응답한다

- 실패시 이름이 중복일 경우 409를 응답한다

 

//index.spec.js

describe('PUT /users/:id', () => {
  describe('성공 시', () => {
    it('변경된 name을 응답한다', done => {
      const name = 'chally';
      request(app)
      	.put("/users/3")
        .send({name})
        .end((err,res) => {
          res.body.should.have.property('name', name);
          done();
        });
    });
  });
  
  describe('실패 시', () => {
    it('정수가 아닌 id일 경우 400을 응답한다', done => {
      request(app)
      	.put("/users/three")
        .expect(400)
        .end(done);
    });
    
    it('name이 없을 경우 400을 응답한다', done => {
      request(app)
      	.put("/users/3")
        .send({})
        .expect(400)
        .end(done);
    });
    
    it('없는 유저일 경우 404를 응답한다', done => {
      request(app)
      	.put("/users/999")
        .expect(404)
        .end(done);
    });
    
    it('이름이 중복인 경우 409를 응답한다', done => {
    	request(app)
        	.put("/users/3")
            .send({name: 'bak'})
            .expect(409)
            .end(done);
    });
  });
});

 

//index.js

app.put("/users/:id", (req,res) => {
  const id = parseInt(req.params.id, 10);
  if(Number.isNaN(id)) return res.status(400).end();
  
  const name = req.body.name;
  if(!name) return res.status(400).end();
  
  const user = users.filter(user => user.id === id);
  if(!user) return res.status(404).end();
  
  const isConflicted = users.filter(user => user.name === name).length;
  if(isConflicted) returnres.tatus(409).end();
  
  user.name = name;
  return res.json(user);
});

 

5. DELETE /users/:id

 

- 성공시 204를 응답한다

- 실패시 id가 숫자가 아닐경우 400을 응답한다

 

describe('DELETE /users/1', () => {
  describe('성공 시', () => {
    it('204를 응답한다', done => {
      request(app)
      	.delete('/users/1')
        .expect(204)
        .end(done);
    });
  });
  
  describe('실패 시', () => {
    it('id가 숫자가 아닐경우 400을 응답한다', done => {
      request(app)
      	.delete('/users/one')
        .expect(400)
        .end(done);
    });
  });
});

 

//index.js

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();
});

 

* 정리

 

HTTP 4가지 메서드의 성공과 실패를 나누고 각각 테스트를 한다.

매개변수의 id값, body에 전송되는 name 등의 유효성을 검증해야한다.

반응형

'학습 > Node.js' 카테고리의 다른 글

노드 ORM 시퀄라이저  (0) 2022.01.17
라우터 컨트롤러 함수로 분리  (0) 2022.01.17
mocha, should, supertest 사용하기  (0) 2022.01.15
강의 후기  (0) 2022.01.15
node js 데이터베이스 이용하기  (0) 2022.01.15