테스트 관련 패키지는 개발할 때만 필요하므로 --save-dev
옵션을 사용한다.
npm i axios
npm i --save-dev mocha chai nyc sinon sinon-chai
mocha는 테스트 실행해주는 패키지다
chai는 assertion 패키지다
nyc는 코드 커버리지를 시각적으로 보여주는 패키지다
axios는 promise 기반 HTTP client다.
sinon-chai는 sinon.js의 mocking 프레임워크를 사용하기 위해 chai 라이브러리와 함께 사용자 정의 assertion을 제공한다.
package.json 파일의 scripts에 다음을 추가한다.
{
"name": "project",
"version": "1.0.0",
"description": "",
"dependencies": {
"axios": "^0.21.1"
},
"devDependencies": {
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.23.4",
"mocha": "^9.0.3"
},
"scripts": {
"test": "mocha",
"coverage": "nyc --reporter=html npm run test"
}
}
test 디렉토리에 {테스트할 파일 이름}.test.js 파일을 생성한다.
// assert, 테스트할 파일에서 메서드를 import 한다.
const assert = require('assert');
const { handler } = require('../handler');
const { expect } = require('chai');
// describe 메서드는 테스트를 구분하고 설명한다.
describe('handler', () => {
// it 메서드는 테스트 상황과 실행 후 결과를 명시하고 테스트할 로직을 실행한다.
it('should return 1 when call handler', function() {
result = handler();
assert.strictEqual(result, 1);
expect(result).to.be.eq(1);
});
});
assertion을 위해 assert 외에 chai에서 제공하는 expect(), should()가 있다.
이것들을 사용하면 테스트에 대한 정보를 더 알려주기 때문에 BDD에 좀 더 가까워지는 것 같다.
터미널에 npm run test
를 입력하면 테스트가 실행된다.
npm run coverage
를 입력하면 테스트가 실행되고 커버리지를 측정해준다.
coverage 디렉토리의 index.html을 브라우저에서 열면 파일마다 테스트 커버리지가 뜬다.
파일명을 클릭하면 테스트가 어떤 코드를 커버하지 못했는지 커버했는지가 표시된다.
비동기 호출을 하면 테스트는 해당 메서드가 언제 끝나는지 모른다. 그래서 it()에서 done을 인자로 넘겨서 비동기 호출이 끝났는지 알려줘야한다.
in app.js
const axios = require('axios');
class User {
constructor(userName, viewRepos = false) {
this.userName = userName;
this.canViewRepos = viewRepos;
}
getUserId() {
return axios.get(`https://api.github.com/users/${this.userName}`)
.then(response => response.data.id);
}
getUserRepo(repoIndex) {
if (this.canViewRepos) {
return axios.get(`https://api.github.com/users/${this.userName}/repos`)
.then(response => response.data[repoIndex])
}
return Promise.reject('Cannot view repos');
}
};
module.exports = {
User
};
in app.test.js
const { User } = require('../app');
const { expect } = require('chai');
const chai = require('chai');
const sinon = require('sinon');
const sinonChai = require('sinon-chai');
const axios = require('axios');
chai.use(sinonChai);
describe('user class', () => {
const sandbox = sinon.createSandbox();
let user;
beforeEach(() => {
user = new User('codebubb');
});
afterEach(() => {
sandbox.restore();
});
it('should get user id', () => {
const getMock = sandbox.stub(axios, 'get').resolves({
data: { id: 123 }
});
console.log(getMock);
user.getUserId()
.then(result => {
console.log(result);
expect(result).to.be.a('number');
expect(result).to.be.eq(123);
expect(getMock).to.have.been.calledOnce;
expect(getMock).to.have.been.calledWith('https://api.github.com/users/codebubb');
})
.catch();
});
it('shloud get repos', (done) => {
sandbox.stub(user, 'canViewRepos').value(true);
const getMock = sandbox.stub(axios, 'get').resolves({ data: [
'1', '2'
]});
user.getUserRepo(0)
.then(response => {
console.log(response);
expect(response).to.be.eq('1');
expect(getMock).to.have.been.calledOnceWith('https://api.github.com/users/codebubb/repos');
done();
})
.catch(done);
});
it('shloud return error if user can not view repos', (done) => {
const getMock = sandbox.stub(axios, 'get').resolves({ data: [
'1', '2'
]});
user.getUserRepo(2)
.catch(error => {
expect(error).to.be.eq('Cannot view repos');
expect(getMock).not.have.been.called;
done();
});
});
});
it()의 테스트 콜백에 인자를 추가하면 mocha는 테스트를 완료하기 위해 이 함수가 호출되기를 기다려야만한다는 것을 안다.
테스트할 때 실제 서버의 API를 호출하게 되면 서버에서 문제가 생겼을 때 테스트 통과가 안된다. mocking을 해서 응답값을 임의로 지정해줄 수 있다. sinon.stub()을 사용해서 mocking할 수 있는데 여러 테스트에서 사용할 수 없다. sinon.createSandbox()를 사용해서 여러 테스트 메서드에서 mocking할 수 있게 해야 한다. 각각의 테스트마다 sandbox를 초기화하기 위해서 afterEach() hook을 사용한다. 테스트 전에 공용으로 생성해야할 모델들은 beforeEach()에서 설정한다.