test("반환값은 첫 번째 매개변수와 두 번째 매개변수를 더한 값이다", () => {expect(add(50,50)).toBe(100);)test("반환값의 상한은 '100'이다.", () => {expect(add(70,80)).toBe(100);})
test 함수를 작성할 때 테스트 코드가 어떤 의도로 작성됐으며, 어떤 작업이 포함됐는지 테스트명으로 명확하게 표현해야다 한다.
에지 케이스와 예외 처리
모듈을 사용할 때 실수 등의 이유로 예상하지 못한 입력값을 보낼 때가 있다.
만약 모듈에 예외 처리를 했다면 예상하지 못한 입력값을 받았을 때 실행 중인 디버거로 문제를 빨리 발견할 수 있다.
타입스크립트로 입력값 제약 설정
타입스크립트를 사용한다면 함수의 매개변수에 타입을 붙여 다른 타입의 값이 할당되면 실행하기 전에 오류를 발생시킨다.
exportfunctionadd(a:number, b:number) {constsum= a + b;if(sum >100){return100; }return sum}
하지만 정적 타입을 붙이는 것만으로는 부족할 때가 있다.
예를 들어 특정 범위로 입력값을 제한하고 싶을 때는 런타임에 예외를 발생시키는 처리를 추가해야한다.
예외 발생시키기
ex: add 함수에 매개변수 a,b는 0에서 100까지 숫자만 받을 수 있다는 조건을 추가해보자.
타입만으로는 커버할 수 없다.
exportfunctionadd(a:number, b:number) {if (a <0|| a >100) {thrownewError("0~100 사이의 값을 입력해주세요"); }if (b <0|| b >100) {thrownewError("0~100 사이의 값을 입력해주세요"); }constsum= a + b;if(sum >100){return100; }return sum}
// 잘못된 예외 검증 코드 작성법expect(add(-10,110)).toThrow();// 올바른 작성법 - 화살표 함수를 사용하면 함수에서 예외가 발생하는지 검증할 수 있다.expect(() =>add(-10,110)).toThrow();
오류 메시지를 활용한 세부 사항 검증
예외 처리용 매처인 toThrow에 인수를 할당하면 예외에 대해 더욱 상세한 내용을 검증할 수 있다.
Error 인스턴스를 생성하면서 메시지를 인수로 할당
test("인수가 '0~100'의 범위 밖이면 예외가 발생한다", () => {expect(() =>add(110,-10)).toThrow('0~100 사이의 값을 입력해주세요");})
의도적으로 예외를 발생시키기도 하지만 의도치 않은 버그가 생겨서 발생할 때도 있다.
의도했는지 아닌지 구분하기 위해 '의도한 대로 예외가 발생하고 있는가'라는 관점으로 접근하자.
instanceof 연산자를 활용한 세부 사항 검증
Error 클래스를 더욱 구체적인 상황에 맞춰 작성해보자.
설계의 폭을 넓힐 수 있다.
Error 클래스를 상속받은 두 개의 클래스 HttpError, RangeError 로 생성된 인스턴스는 instanceof 연산자를 사용해서 다른 인스턴스와 구분할 수 있다.
exportclassHttpErrorextendsError {}exportclassRangeErrorextendsError {}if (err instanceofHttpError) {// 발생한 오류가 HttpError인 경우}if (err instanceofRangeError) { // 발생한 오류가 RangeError인 경우}
상속받은 클래스들을 활용해 입력값을 체크하는 함수를 작성해보자.
functioncheckRange(value:number) {if (value <0|| value >100) {thrownewRangeError('0~100 사이의 값을 입력해주세요'); }}
기존 add 함수에서 a,b를 검증해서 예외를 발생시키는 부분을 checkRange 함수 한곳에서 처리할 수 있어서 더 좋은 코드가 된다.
exportfunctionadd(a:number, b:number) {checkRange(a);checkRange(b);constsum= a + b;if(sum >100){return100; }return sum}
toThrow 매처의 인수에는 메시지뿐만아니라 클래스도 할당이 가능하다.
예외가 특정 클래스의 인스턴스인지 검증할 수 있다.
// 발생한 예외가 RangeError이므로 실패expect(() =>add(110,-10)).toThrow(HttpError);// 발생한 예외가 RangeError이므로 성공expect(() =>add(110,-10)).toThrow(RangeError);// 발생한 예외가 Error를 상속받은 클래스이므로 성공 - Error를 상속받은 클래스이므로 테스트 성공함 (주의)expect(() =>add(110,-10)).toThrow(Error);
// Promise를 리턴하는 방법 - catch 메서드에 전달할 함수에 단언문을 작성test("지정 시간을 기다린뒤 경과 시간과 함께 reject 된다.", () => {returntimeout(50).catch((duration) => {expect(duration).toBe(50); })})// rejects 매처를 사용하는 단언문 활용 - 단언문을 리턴하거나 async/await을 사용test("지정 시간을 기다린뒤 경과 시간과 함께 reject 된다.", () => {returnexpect(timeout(50)).reject.toBe(50);})test("지정 시간을 기다린뒤 경과 시간과 함께 reject 된다.",async () => {awaitexpect(timeout(50)).reject.toBe(50);})// try/catch 문을 사용하는 방법 Unhandled Rejection을 try 블록에서 발생시키고, // 발생한 오류를 catch 블록에서 받아 단언문으로 검증test("지정 시간을 기다린뒤 경과 시간과 함께 reject 된다.",async () => {expect.assertions(1); // 단언문이 한 번 실행되는 것을 기대하는 테스트가 된다.try {awaittimeout(50); } catch (err) {expect(err).toBe(50); }})
테스트 결과가 기댓값과 일치하는지 확인
실수로 작성된 코드가 있을 때 실행하고 싶은 단언문에 도달하지 못한 채로 성공하며 종료된다.
test("지정 시간을 기다린뒤 경과 시간과 함께 reject 된다.",async () => {try {awaitwait(50); // timeout 함수를 사용할 생각이었지만 실수로 wait 함수를 사용함// 오류가 발생하지 않으므로 여기서 종료되면서 테스트는 성공 } catch (err) {// 단언문은 실행되지 않음expect(err).toBe(50); }})
이와 같은 실수를 하지 않으려면 테스트 함수 첫 줄에 expect.assertions를 호출해야 한다.
이 메서드는 실행되어야 하는 단언문의 횟수를 인수로 받아 기대한 횟수만큼 단언문이 호출됐는지를 검증한다.
test("지정 시간을 기다린뒤 경과 시간과 함께 reject 된다.",async () => {expect.assertions(1); // 단언문이 한 번 실행되는 것을 기대하는 테스트가 된다.try {awaitwait(50); // 단언문이 한 번도 실행되지 않은채로 종료되므로 테스트는 실패 } catch (err) {expect(err).toBe(50); }})
비동기 처리 테스트를 할 때는 첫 번째 줄에 expect.assertions 을 추가하면 사소한 실수를 줄일 수 있다.
비동기 처리 테스트는 다양한 방식으로 작성할 수 있으니 자유롭게 선택하면 되지만 .resolves, .rejects 매처를 사용할 떄는 주의해야 한다.
테스트가 성공한것이 아니라 (*주의)단언문이 한 번도 평가되지 않고 종료될 때에도 테스트는 성공한다.
test("return하고 있지 않으므로 Promise가 완료되기 전에 테스트가 종료된다.", () => {// 실패할 것을 기대하고 작성한 단언문expect(wait(2000)).resolves.toBe(3000);// 올바르게 고치려면 다음 주석처럼 단언문을 return 해야 한다.// return expect(wait(2000)).resolves.toBe(3000);})
비동기 처리를 테스트할 때 테스트 함수가 동기 함수라면 반드시 단언문을 return 해야 한다.
테스트에 따라서는 단언문을 여러 번 작성해야 할 때가 있는데 단언문을 여러번 작성하다 보면 return 하는 것을 잊기 쉽다.
이같은 실수를 하지 않으려면 비동기 처리가 포함된 테스트를 할때 다음 과 같은 규칙을 갖고 접근해야 한다.
비동기 처리가 포함된 부분을 테스트할 때 테스트 함수를 async 함수로 만든다.
.resolves, .rejects 가 포함된 단언문은 await 한다.
try-catch 문의 예외 발생을 검증할 때는 expect.assertions 를 사용한다.