- Published on
NestJS ์์ UNIT TEST ๐๐ปโโ๏ธ
- Authors
- Name
- Haneul
Jest
NestJS ์์ UNIT TEST ๐๐ปโโ๏ธ
jwt.service.spec.ts
describe('JwtService', () => {
let service: JwtService;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
JwtService,
{ provide: CONFIG_OPTIONS, useValue: jwtOptions },
],
}).compile();
service = module.get<JwtService>(JwtService);
});
jwt service ๋ jsonwebtoken ํจํค์ง ์ด์ธ์๋ ๋ฌ๋ฆฌ mocking ํ ๊ฒ์ด ์๋ค. jwt service๊ฐ ์ ๋๋ก ์ค๋น ๋์๋์ง ํ์ธํ๋ ค๋ฉด
it('should be defined',()=>{ expect(service).toBeDefined();});
์ด ์ฝ๋๋ก ํ์ธํด๋ณผ ์ ์๋ค.
jest.mock('jsonwebtoken', () => ({
sign: jest.fn(() => MOCKED_TOKEN),
verify: jest.fn(() => ({ id: TEST_ID })),
}))
describe('Test sign method', () => {
it('should return MOCKED_TOKEN', () => {
const token = service.sign(TEST_ID)
expect(jwt.sign).toHaveBeenCalledTimes(1)
expect(jwt.sign).toHaveBeenCalledWith({ id: TEST_ID }, TEST_PRIVATE)
expect(token).toEqual(MOCKED_TOKEN)
expect(typeof token).toEqual('string')
})
})
jest.mock('jsonwebtoken', () => ...) ์ด ๋ธ๋ญ์ jsonwebtoken ํจํค์ง๋ฅผ mockingํ๋ ๊ฒ์ด๋ค.
์ ๋ํ ์คํธ์ด๊ธฐ ๋๋ฌธ์ jwt service ์์ฒด์๋ง ์ง์คํด์ผ ํ๊ธฐ ๋๋ฌธ์ ์ด๋ฌํ ์ํฅ์ ์ค ์ ์๋ ํจํค์ง๋ ํจ์, ํด๋์ค๋ค์ mockingํ์ฌ ํ ์คํธ์ ๋๊ตฌ๋ก ์ด์ฉํ๋ค.
๋ฐ์ expect(jwt.sign).toHaveBeenCalledTimes(1);์ jwt.sign์ด ๋จ ํ ๋ฒ ํธ์ถ ๋๋์ง ํ ์คํธ๋ก ํ์ธํ๋ ๊ฒ์ด๋ค.
expect์ ์ค์ ์ ๋ค๋ฅด๋ฉด ํ ์คํธ๋ ์คํจํ๋ค.
describe, it ์ ์ ์ ํ ์ ์ฌ์ฉํ๋ฉด ํ ์คํธ ์ฝ๋ ๊ฐ๋ ์ฑ์ด ๋์์ง๋ค. describe๋ ํ ์คํธํ ๋์์ ๋ํด ์ค๋ช ํ๋ค.
it(individual test), ๊ทธ ๋์์ ๊ฐ๋ณ์ ์ผ๋ก ํ ์คํธ ํ๋ ๊ฒ์ ์๋ฏธํ๋ค.
users.service.spec.ts
describe('UsersService', () => {
let service: UsersService;
let jwtService: JwtService;
let userRepository: MockRepository<User>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: getRepositoryToken(User),
useValue: mockRepository(),
},
{
provide: JwtService,
useValue: mockJwtService,
},
],
}).compile();
service = module.get<UsersService>(UsersService);
userRepository = module.get(getRepositoryToken(User));
jwtService = module.get<JwtService>(JwtService);
});
๋จผ์ user service๋ง ํ ์คํธํด์ผ ํ๋๋ฐ, repository๋ ์ฐ๊ฒฐ๋์ด ์๊ณ , user ๋ก๊ทธ์ธ ์ฒ๋ฆฌํ ๋ ์ฌ์ฉํ๋ jwt service๋ ์ฐ๊ฒฐ๋์ด ์์ต๋๋ค. user service์ ์ง์คํด์ผ ํ๋ ๋จ์ ํ ์คํธ์ด๋ฏ๋ก, ์ด๊ฒ๋ค์ mocking์ ํด์ผ ํฉ๋๋ค. jwtService๋ jwt.service์ ๋ง์ฐฌ๊ฐ์ง๋ก ์ฌ๊ธฐ์๋ ๋ค์ mocking์ ํด์ฃผ๊ณ , repository๋ ์ฝ๊ฐ ์์ํ์ง๋ง ์์ ์ฝ๋์ ๊ฐ์ด ์ค์ ์ ํด์ค๋๋ค.
์ฝ๋๋ฅผ ๋ณด์๋ฉด jwt.service.spec.ts์๋ ๋ฌ๋ฆฌ users.service.spec.ts์์๋ beforeAll์ด ์๋๋ผ beforeEach๋ก ๋ชจ๋์ ์ด๊ธฐํํ์ต๋๋ค.
์ด ๋์ ์ฐจ์ด๋ ๋ญ๊น์?
ํ ์คํธ๋ฅผ ์งํํ๊ธฐ ์ ์ ์์ jwt ํ ์คํธ์์ ํ ๊ฒ์ฒ๋ผ ์ธํ ์ด ํ์ํฉ๋๋ค. ์ธํ ์ ๋งค ํ ์คํธ๋ง๋ค ํ ๊ฒ์ธ์ง ๋๋ ์ ์ฒด ํ ์คํธ์์ ํ ๋ฒ๋งํด๋ ๋๋์ง์ ์ฐจ์ด์ ๋๋ค.
์๋ฅผ ๋ค์ด, mockingํ respository ๊ฐ์ ๊ฒฝ์ฐ์๋ createAccount ํ ์คํธ์๋ ์ฌ์ฉํ ์๊ฐ ์๊ณ , seeProfile, editProfile ํ ์คํธํ๋ ๊ฒฝ์ฐ์๋ ๋ชจ๋ ์ฌ์ฉ๊ฐ๋ฅํ๋ฐ, ๋งค๋ฒ ์ฌ์ฉํ์๋ ๋ค์ด ๊ฐ ๋ณ์๋ค์ ์ด๊ธฐํํ์ง ์์ผ๋ฉด ํ ์คํธ๊ฐ ๊ผฌ์ผ ์ ๋ฐ์ ์์ต๋๋ค. ๊ทธ๋์ user.service๋ฅผ ํ ์คํธํ ๋์๋ beforeAll์ด ์๋๋ผ beforeEach๊ฐ ๋๋ ๊ฒ์ ๋๋ค.
async createAccount({
email,
password,
role,
}: CreateAccountInput): Promise<CreateAccountOutput> {
try {
const exists = await this.users.findOne({ email });
// test 1
if (exists) {
return { ok: false, error: `There is a user with that email already` };
}
const user = this.users.create({
email,
password,
role,
});
await this.users.save(user);
// test 2
return {
ok: true,
error: null,
};
} catch {
// test 3
return {
ok: false,
error: 'Could not create account',
};
}
}
1์ ๊ฒฝ์ฐ - ์ด๋ฏธ ์กด์ฌํ๋ ์ด๋ฉ์ผ์ด ์์ ๊ฒฝ์ฐ์๋ creatAccount๊ฐ ์คํจํด์ผ ํฉ๋๋ค.
2์ ๊ฒฝ์ฐ - ์ ์์ ์ผ๋ก ๊ณ์ ์ด ๋ง๋ค์ด์ ธ์ผ ํ๋ ๊ฒฝ์ฐ์ ๋๋ค.
3์ ๊ฒฝ์ฐ - findOne์ด๋ save ๋ฉ์๋์์ ์๋ฌ๊ฐ ๋ฐ์ํ์ฌ catch๋ก ๋์ด๊ฐ๋ ๊ฒฝ์ฐ์ ๋๋ค.
์์ createAccount์์๋ ์์ logic๋ค์ ํ๋จํด์ผ๋ง ์ฝ๋ ๋ชจ๋ ๋ถ๋ถ๋ค์ ํ ์คํธ ํ๋ ๊ฒ์ผ๋ก ๋ณผ ์ ์์ต๋๋ค. ๊ทธ๋์ ํ ์คํธ๋
it('should success to create User', async () => { ...
it('should fail because of user existing', async () => { ...
it('should fail because of saving failed', async () => { ...
์ด๋ ๊ฒ ์ธ ๊ฐ์ง ๋ก์ง์ผ๋ก ํ ์คํธ๋ฅผ ํ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ฉด createAccount์ ๋ํ test coverage๊ฐ ์ ๋ถ ์ฑ์์ง๋๋ค.
์์ it(individual test) ์ฝ๋๋ฅผ ์กฐ๊ธ๋ ์ดํด ๋ณด๋ฉด ํ ์คํธ๊ฐ ์ด๋ค ์์ผ๋ก ์งํ์ด ๋์ด์ผ ํ๋์ง ๊ฐ์ ์ก์ ์ ์์ต๋๋ค.
it('should success to create user', ...
createAccount๋ฅผ ํธ์ถํด์ ์ฑ๊ณต์ ์ผ๋ก ๊ณ์ ๋ง๋๋ ๊ฒ์ ํ ์คํธ ํ๋ ๊ฒ์ ๋๋ค. service์ ๋ก์ง๋ง ํ ์คํธํ๋ ๊ฒ์ด๋ฏ๋ก respository๋ ํ๋ด๋ด๊ธฐ๋ฅผ ํด์ผ ํฉ๋๋ค. users.service.spec.ts ์๋จ ์ฝ๋์ ๋ณด๋ฉด ์ด๋ฏธ userRepository๋ฅผ ํ๋ด๋ด๊ณ ์ค์ ์ ํด๋จ์ต๋๋ค. beforeEach๋ฅผ ํตํด์ ๋งค ํ ์คํธ๋ง๋ค ์ด๊ธฐํ๊ฐ ๋๊ธฐ ๋๋ฌธ์ ๊ฑฑ์ ํ์ง ์๊ณ ํ ์คํธ ์์์ ๋ฆฌํด ๊ฐ์ ํ๋ด๋ผ ์ ์์ต๋๋ค.
userRepository.findOne.mockResolvedValueOnce(null);
user repository์ findOne๊ฐ์ ํ๋ด๋ธ ๊ฒ์ ๋๋ค. mockResolvedValue๋ Promise๋ฅผ ๋ฆฌํดํฉ๋๋ค. Promise์ resolve๊ฐ์ด null์ด๋ผ๋ ๋ป์ด ๋๊ฒ ์ต๋๋ค. ์ค์ db๋ผ๋ฉด db ์ฐ๊ฒฐ์ด๋ ์ํ์ ๋ฐ๋ผ ๊ฐ์ ๋ฆฌํด ๋ชปํ ์๋ ์๊ณ ์ฐ๊ฒฐ์ด ์๋ ์๋ ์๊ธฐ ๋๋ฌธ์ ์ ๋ ํ ์คํธ๋ผ๋ ์๋ฏธ๊ฐ ์ด์ง ์๊ธฐ ๋๋ฌธ์ ์ด๋ค ๊ฒฝ์ฐ๋ผ๋ db์๋ ๊ด๊ณ ์์ด null ๊ฐ์ resolve๋ก ๋๊ฒจ์ค๋ค๋ ๋ป์ด ๋ฉ๋๋ค.
userRepository.create.mockReturnValueOnce(hostArgs);
create๋ typeorm repository์์ Promise๋ก ๋ฆฌํด์ ํ๋ ๊ฒ์ด ์๋๋ผ, ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ๋ฆฌํดํด์ค๋๋ค. ์ธ์ด๋ธ ๋๊ธฐ ์ด์ ๊ฐ์ ๋๋ค. ๊ทธ๋์ create๋ resolve ๊ฐ์ ๋ฆฌํดํ๋ ๊ฒ์ด ์๋๋๋ค. ๊ทธ๋ฅ ๊ฐ์ฒด๋ฅผ ๋ฆฌํดํด์ฃผ๊ธฐ ๋๋ฌธ์ ์์ ๊ฐ์ด ์ค์ ์ ํด์ฃผ๋ฉด ๋๊ฒ ์ต๋๋ค.
userRepository.save.mockResolvedValue(TEST_HOST);
findOne๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก resolve ๊ฐ์ ๋ฆฌํดํด์ค๋๋ค. ์ฑ๊ณต์ ์ผ๋ก ๋ง๋ค์ด์ง ๊ฒฝ์ฐ์ด๋ฏ๋ก db์ ์ฑ๊ณต์ ์ผ๋ก ์ ์ฅ๋ ๊ฐ์ ๋ฆฌํดํ๋ repository์ save ๋ฉ์๋๋ฅผ ํ๋ด๋ธ ๊ฒ์ ๋๋ค.
describe('createAccount Testing', () => {
it('should success to create User', async () => {
userRepository.findOne.mockResolvedValueOnce(null)
userRepository.create.mockReturnValueOnce(hostArgs)
userRepository.save.mockResolvedValue(TEST_HOST)
const result = await service.createAccount(hostArgs)
expect(userRepository.findOne).toHaveBeenCalledTimes(1)
expect(userRepository.findOne).toHaveBeenCalledWith({
email: hostArgs.email,
})
expect(userRepository.create).toHaveBeenCalledTimes(1)
expect(userRepository.create).toHaveBeenCalledWith(hostArgs)
expect(userRepository.save).toHaveBeenCalledTimes(1)
expect(userRepository.save).toHaveBeenCalledWith(hostArgs)
expect(result).toMatchObject({ ok: true, error: null })
})
})
createAccount๊ฐ ํธ์ถ๋์์ต๋๋ค. ์ฐ๋ฆฌ๊ฐ ์ค์ ํด ๋์ ๊ฐ์ ์ํ๋ฉด ์ ์ฝ๋๋ findOne์ ํธ์ถํด์ ์ด๋ฏธ ์กด์ฌํ๋ ์ด๋ฉ์ผ ๊ณ์ ์ด ์์์ ํ์ธํ๊ณ create ๋ฉ์๋๋ฅผ ํธ์ถํด์ entity๋ฅผ ๋ง๋ค๊ณ , save ๋ฉ์๋๋ฅผ ํธ์ถํด์ db์ ์ํฐํฐ๋ฅผ ์ ์ฅํ๋ ๋ก์ง์ผ๋ก ํ๋ฌ ๊ฐ ๊ฒ์ ๋๋ค. ์ฐ๋ฆฌ๊ฐ ๋ง๋ createAccount๊ฐ ์์ ๋ก์ง๋๋ก ํ๋ฌ๊ฐ๋ค๊ณ ๊ธฐ๋ํ์์ผ๋ฏ๋ก, ์ค์ ๋ก ๊ทธ๋ ๊ฒ ํธ์ถ์ด ๋์๋ ํ์ธ๋ง ํด์ฃผ๋ฉด ํ ์คํธ๋ฅผ ์ฑ๊ณต์ ์ผ๋ก ํ ๊ฒ์ ๋๋ค.
์์ ๋ก์ง๋๋ก findOne์ด ํธ์ถ๋์๋๊ฐ(toHaveBeenCalledTimes), ํ๋ผ๋ฏธํฐ๋ ์ ๋๋ก ์ ๋ ฅ ๋์๋๊ฐ(toHaveBeenCalledWith), create๋ ์ ๋๋ก ํธ์ถ๋์๋๊ฐ, save๋ ์ ๋๋ก ํธ์ถ๋์๊ณ , ๊ฒฐ๊ณผ๊ฐ ์ฐ๋ฆฌ๊ฐ ์ํ๋ ๊ฐ(toMatchObject)์ด ๋์ค๋๊ฐ?๋ฅผ jest์์ ํ๋จ์ ํด์ฃผ๊ธฐ ๋๋ฌธ์ createAccount๊ฐ ์ ์์ ์ผ๋ก ์๋ํ๋ ๊ฒฝ์ฐ ํ ์คํธ๊ฐ ๋๋ ๊ฒ์ ๋๋ค.
createAccount๋ฅผ ๋ชจ๋ ํ ์คํธํ๋ ค๋ฉด, ์์์ ์ธ๊ธ๋๋ฆฐ ๋๋ก, ์ด๋ฏธ ์ด๋ฉ์ผ ๊ณ์ ์ด ์ด๋ฏธ ์๋ ๊ฒฝ์ฐ์, save ๋ฉ์๋๊ฐ ์คํจํ ๊ฒฝ์ฐ๋ฅผ ๋ชจ๋ ํ ์คํธํด์ผ full coverage๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
์ฐธ๊ณ : jset ๊ณต์๋ฌธ์, jset mocking,