[Angular Test/번역] Testing with Mocks & Spies using createSpyObj in Angular
TDD의 T자도 모르고 개발을 하다가, 개발 코스트를 줄이기 위해 테스트의 중요성을 깨닫게 되어 이번에 새롭게 공부하게 됐습니다.
그렇게 공부하면서 만난 spy
, mocking
.. 이런 키워드가 구글링해봐도 명확한 개념을 찾기 어려웠는데,
이 원본 포스트에서 도움을 받아 다른 분들께도 도움이 됐으면 하는 바람으로 번역을 공유해봅니다. 😆😆😆
Testing with Mocks & Spies using createSpyObj in Angular
Here I am going to explain a very simple way to write test the services injected in your Angular component.
luardo.medium.com
앵귤러의 createSpyObj
를 이용해 Mocks & Spies로 테스팅하기
지금부터 앵귤러 컴포넌트에 주입된 services를 테스트하는 매우 심플한 방법을 설명하고자 합니다.
제가 싫어하는 것 중 하나는 개발팀에서 몇몇의 개발자들이 "이 기능 개발은 끝났고, 이제 테스트만 작성하면 됩니다."라고 말하는 겁니다. 그럴 때마다 전 늘 이렇게 말하고 싶어요.
"음, 그럼 그 기능은 아직 안 끝난겁니다." - 하지만 제가 전형적인 오만하고 toxic한 개발자가 되고 싶지 않아요.
어쨌든, 이 글을 보는 당신은 테스팅의 중요성을 알고 있기 때문에 여기로 온 것일테니 저는 기쁩니다.
하지만 주입할 dependencies가 있다면 컴포넌트의 테스트 코드를 작성하는 것은 짜증나고 까다로울 수 있습니다.
우선, 실제 service class를 provider array에 추가하지 않는 것이 중요합니다. 이건 좋은 방법이 아니에요. 항상 mock을 사용하세요.
문제 사항
컴포넌트에 사용되는 service를 테스트할 때마다, 내부 메소드를 overwrite한 mocked service class를 생성해야 합니다.
테스트를 위해서는 mock service를 만들어야 된다는 얘기를 들어봤을 겁니다. 그리고 보통은 mocked class를 이런 식으로 생성할 겁니다.
class MockUserService {
getFirstName() {
return of('Joe');
}
getLastName() {
return of('Rogan');
}
}
그리고 provider array에 해당 service를 추가합니다.
TestBed.configureTestingModule({
providers: [
{
provide: UserService,
useValue: MockUserService
}
]});
하지만 컴포넌트에 주입된 services가 많다면, 이러한 보일러 플레이트가 투머치하다고 느껴질 수 있습니다.
createSpyObj 사용하기
이는 Spy 오브젝트를 생성함으로써 services를 mocking할 수 있는 심플한 방법입니다.
이 Spy는 Jasmine의 기능으로, 모든 함수를 stub(=테스트에서 종속성을 없애기 위해 제어가능한 대체제로 만듦)하며 함수 호출을 추적할 수 있게 합니다. 주로 함수나 오브젝트를 mocking하는 데 사용됩니다.
하지만 여기서 spy와 spyObj는 다르다는 것을 명심하세요.
jasmine.createSpy: 관찰할 함수가 없을 경우에 사용
jasmine.createSpyObj: 관찰할 메소드의 mocks를 생성할 때 사용됩니다. spy에 정의된 각각의 프로퍼티가 담긴 오브젝트를 리턴합니다.
예시
describe('MyComponent', () => {
const mockedUserService = jasmine.createSpyObj('MyRealService', ['getFirstName', 'getLastName', 'getId']);
beforeEach(() => TestBed.configureTestingModule({
providers: [{
provide: UserService,
useValue: mockedUserService
}]
}));
});
그래서 createSpyObj는 첫번째 파라미터에 mocking하길 원하는 service의 이름, 두번째 파라미터에 해당 클래스의 모든 메소드들을 배열에 정의합니다.
위 예시같은 경우엔, getFirstName, getLastName, getId 이렇게 3개의 메소드를 테스트할 겁니다.
테스트를 진행할 때 어떤 값을 리턴하는 service를 원한다면, 아래처럼 service들이 리턴할 값을 설정해줘야 합니다.
mockedUserService.getFirstName.and.returnValue(of('Joe'))
앵귤러에선 Observables를 사용하기 때문에, mock service에서 rxjs의 오퍼레이터인 of를 사용해 문자열 Observable을 리턴해야 합니다.
따라서 모든 메소드에 이렇게 적용할 수 있습니다.
mockedUserService.getFirstName.and.returnValue(of('Joe'));
mockedUserService.getLastName.and.returnValue(of('Rogan'));
mockedUserService.getId.and.returnValue(of(123));
그리고... 이게 다입니다!
이제 다음과 같이 assertions를 작성할 수 있는 Spy를 갖게 됩니다.
it('should call getFirstName', () => {
expect(mockedUserService.getFirstName).toHaveBeenCalled();
});
위처럼 사용하려는 용도에 맞게 작성하면 됩니다.
테스트를 작성하는 것은 중요하지만, 동시에 짜증나는 일이기도 합니다.
실제 service class를 우리의 testbed에 주입시키지 않아야 하며, 항상 mock 또는 stub service를 생성해야 합니다.
createSpyObj를 이용해 매우 심플하고 빠르게 간단하게 service를 mocking할 수 있습니다.
코드 단 한 줄로 stub class와 spy를 생성할 수 있죠.