Testing?
- 하나의 기능을 테스트하기 위해 여러 다른 기능을 사용하게 된다.
- 이는 하나만 테스트하기엔 좋지 못하다.
- 그렇기에, 다른 의존성은 가짜로 생성해 테스트를 하게 된다.
Unit Testing
Setup
- 테스트 관련 파일은 .spec. 을 중간에 붙힌다.
it('create instance of auth service', async () => {
// 테스트를 위한 가짜 Service
const fakeUsersService = {
find: () => Promise.resolve([]),
create: (email: string, password: string) => Promise.resolve({id: 1, email, password})
}
// 테스트를 위한 DI를 만든다.
// UsersService 는 테스트 대상이 아니므로 가짜를 만들어 사용한다.
const module = await Test.createTestingModule({
providers: [
AuthService,
{
// UsersService 를 필요로 할시 fake값을 사용하기.
provide: UsersService,
useValue: fakeUsersService,
}
]
}).compile();
// DI로부터 AuthService를 가져옴.
const service = module.get(AuthService);
// 제대로 생성되었는지 테스트.
// 이 과정에서 AuthService는 UsersService를 의존성으로서 필요로 하기에
// 이 또한 확인이 된다.
expect(service).toBeDefined();
})
가짜 데이터 Typing
const fakeUsersService: Partial<UsersService> = {
find: () => Promise.resolve([]),
create: (email: string, password: string) => Promise.resolve({id: 1, email, password} as User)
}
- 일부만 구현할수 있기에 Partial 로 타입
- 이 외 부분도 타입에 맞게 작성.
describe()
- describe("이름", { 테스트들 }) 을 통해 위 it 테스트를 묶을수 있다.
beforeEach()
- 안에 함수를 집어넣을수 있으며, 모든 테스트 이전에 beforeEach 안의 코드가 실행된다.
전체 다 적용된 코드 예시
describe("AuthService", () => {
let service: AuthService;
beforeEach(async () => {
// 테스트를 위한 가짜 Service
const fakeUsersService: Partial<UsersService> = {
find: () => Promise.resolve([]),
create: (email: string, password: string) => Promise.resolve({id: 1, email, password} as User)
}
// 테스트를 위한 DI를 만든다.
// UsersService 는 테스트 대상이 아니므로 가짜를 만들어 사용한다.
const module = await Test.createTestingModule({
providers: [
AuthService,
{
provide: UsersService,
useValue: fakeUsersService,
}
]
}).compile();
// DI로부터 AuthService를 가져옴.
service = module.get(AuthService);
})
it('create instance of auth service', async () => {
expect(service).toBeDefined();
})
})
Signup 함수를 테스트하는 예시
it('create new user', async () => {
const user = await service.signup('email@email.edu', 'password');
// 해싱이 되어 값이 달라졌는지 확인
expect(user.password).not.toEqual('password');
// 제대로 해싱이 되어 점으로 구분이 되었는지 확인
const [salt, hash] = user.password.split('.')
expect(salt).toBeDefined()
expect(hash).toBeDefined()
})
Integration test
- 전체적인 기능을 테스트하는 과정.
- end to end 테스트로도 불린다.
- npm run test:e2e 로 가능하다.
- 기능.e2e-spec.ts 로 이름짓는다.
사용법
describe('Authentication System', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('handles a signup request', () => {
const email = 'asdlkjq@akl.com';
return request(app.getHttpServer())
.post('/auth/signup')
.send({ email, password: 'alskdfjl' })
.expect(201)
.then((res) => {
const { id, email } = res.body;
expect(id).toBeDefined();
expect(email).toEqual(email);
});
});
});
e2e 테스트에 대해 middleware 설정하기
- 앱모듈 자체를 이용해 실행 후 테스트하기에 pipe나 cookiesession 을 설정해야 한다.
- main.ts 가 아닌 앱 모듈에 이들을 설정하면 된다.
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'sqlite',
database: 'db.sqlite',
synchronize: true,
entities: [User, Report],
}),
UsersModule,
ReportsModule],
controllers: [AppController],
providers: [
AppService,
// 앱 파이프를 통해 ValidationPipe를 추가한다.
{
provide: APP_PIPE,
useValue: new ValidationPipe({
whitelist: true
})
}
],
})
export class AppModule {
// 미들웨어를 여기에 실행시킨다.
// Ex. Cookie Session
configure(consumer: MiddlewareConsumer) {
consumer.apply(cookieSession({
keys: ['anythingyouwant']
})).forRoutes("*")
// 모든 라우팅에 사용하도록 지정.
}
}