자바 개발자가 가장 많이 사용하는 테스팅 기반 프레임워크입니다.
JUnit5는 자바 8 부터 사용이 가능하며 테스트 작성자를 위한 API 모듈과 테스트 실행을 위한 API가 분리되어 있습니다.
org.junit.jupiter.api.Assertions 클래스는 값 검증을 위한 assert로 시작하는 static 메서드를 제공하고 있습니다.
하단의 Object 의 값은 비즈니스 모델의 함수를 호출하며 반환 값을 이용해 테스트를 진행할 수 있습니다.
더 많은 Assertions 메서드를 보고싶을 때 해당 링크 를 들어가면 더 많은 메서드를 활용할 수 있습니다. 활용 블로그
사전적 의미로 '테스트를 위해 만든 모형'을 의미하고, 테스트를 위해 실제 객체와 비슷한 모의 객체를 만드는 것을 모킹(Mocking) , 모킹한 객체를 메모리에서 얻어내는 과정을 목업 ( Mock-up ) 이라고 합니다. 이러한 복잡한 객체를 테스트하기 위해 실제 객체와 비슷한 가짜 객체를 만들어 테스트에 필요한 기능만 가지게 하면 테스트가 쉬워집니다. 또한 복잡한 의존성을 가지고 있을 때, 모킹한 객체를 이용하면 의존성을 단절시킬 수 있어 쉽게 테스트할 수 있습니다.
MockMVC 는 Controller 테스트를 위한 객체이며 perform() 메소드를 지원해 해당 메소드를 통해 Controller 호출 테스트를 할 수 있습니다.
@AutoConfigureMockMvc : 무조건
테스트 클래스 상단에 명시해 주셔야합니다.
또한 gradle로 설치시 다음과 같은 의존성이 있어야 합니다.
testCompile("org.springframework.boot:spring-boot-starter-test")
@InjectMocks
private Controller userController;
MockMvc mockMvc;
@BeforeEach
public void init() {
mockMvc = MockMvcBuilders.standaloneSetup(userController).build();
}
mvc.perform(MockMvcRequestBuilders
.get("API 주소")
.accept(MediaType.APPLICATION_JSON))
.param("number1", "1") // .params("number1", "1" , "number2" , "2")
.header("Authorization", result.getAccessToken()))
.andExpect(status().is3xxRedirection());
만약 결과를 받고 싶다 하면 getResponse()
메서드를 활용할 수 있는데 코드는 다음과 같습니다.
ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.get("/user"));
MvcResult mvcResult = resultActions.andExpect(status().isOk()).andReturn();
String str = mvcResult.getResponse().getContentAsString();
우선 API 호출을 한 결과를 resultActions
변수에 받고 그 결과를 .andExpect 메서드를 활용해 200 결과인지 확인한 다음 getResponse() 메서드를 활용해 결과값을 가져와 getContentAsString(); 로 문자열로 파싱합니다.
참고 : https://insight-bgh.tistory.com/507
https://scshim.tistory.com/317
AssertJ는 assertion을 제공하는 자바 라이브러리로 에러 메세지와 테스트 코드의 가독성을 높여주는 라이브러리입니다.
JUnit5에서 제공하는 Assertions의 assert는 인자 순서가 헷갈릴 수 있지만, AssertJ의 Assertions은 가독성이 좋고 헷갈리지 않습니다.
예시 )
assertEquals(expected, actual); // JUnit5
assertThat(actual).isEqualTo(expected); // AssertJ
의존성 : testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.21.0'
assertThat("문자열..") // 해당 문자열의 결과값을 테스트 합니다.
.isNotEmpty() // Null 값이 아니며
.contains("Nice") // "Nice"를 포함하고
.contains("world") // "world"도 포함하고
.doesNotContain("ZZZ") // "ZZZ"는 포함하지 않으며
.startsWith("Hell") // "Hell"로 시작하고
.endsWith("u.") // "u."로 끝나며
.isEqualTo("문자열.."); // "문자열.." 과 일치합니다.
// 정리
- isNotEmpty() // 비어있지 않을 경우
- isNotNull() // Null 이 아니면
- startsWith("문자열") // "문자열"로 시작하면
- endsWith("s.") // "s." 로 끝나면
- doesNotContain("aaa") // "aaa"는 포함하지 않으며
- contains("Java") // "Java"를 포함하고
- contains("Success") // "Success"도 포함하며
- isEqualTo("문자열") // "문자열" 을 equals 메서드로 비교시 true이면
- isNotEqualTo("문자열") // "문자열" 을 equals 메서드로 비교시 false이면
- isSameAs("문자열") // 주소값이 같은 문자열 일경우
- isNotSameAs("문자열") // 주소값이 다른 문자열 일경우
- isInstanceOf(String.class) // String 클래스이고
- isInstanceOf(CharSequence.class) // String 이 구현한 CharSequence 인터페이스이기도 하고
- isNotInstanceOf(Character.class) // Character 클래스는 아니며
assertThat(100) // 정수형 100 을 테스트합니다.
.isPositive() // 양수이며
.isGreaterThan(3) // 3보다 크며
.isLessThan(4) // 4보다 작습니다
.isEqualTo(3, offset(1d)) // 오프셋 1 기준으로 3과 같고
.isEqualTo(3.1, offset(0.1d)) // 오프셋 0.1 기준으로 3.1과 같으며
.isEqualTo(10 , offset(2d)) // 오프셋 2 기준으로 10과 같으며
.isEqualTo(3.14); // 오프셋 없이는 3.14와 같습니다
//정리
.isBetween(8.67, 9d) // 8.67 이상 9 이하이고
.isStrictlyBetween(8.66, 9d) // 8.66 초과 9 미만이며
.isCloseTo(8, offset(0.67d)) // 8과의 오차범위가 0.67 이내이고
.isCloseTo(8, withPercentage(10)) // 8과의 오차범위가 10퍼센트 이내이며
.isNotCloseTo(8, offset(0.66d)) // 8과의 오차범위가 0.66 초과이고
.isNotCloseTo(8, withPercentage(1)) // 8과의 오차범위가 1퍼센트 초과이며
.isGreaterThan(8) // 8보다 크고
.isLessThan(9) // 9보다 작으며
.isPositive() // 양수이고
.isNotNegative() // 음수가 아니고
.isNotZero() // 0이 아니며
.isFinite() // 유한한 숫자이고
.isNotNaN() // NAN 이 아니며
.isEqualTo(8, offset(1d)) // 8과의 차이가 오차범위 1 이내이고
.isEqualTo(8.6, offset(0.1d)) // 8.6과의 차이가 오차범위 0.1 이내이며
.isEqualTo(8.67) // 오프셋 없이는 8.67와 같습니다
.toString();
그 외에 더 많은 검증 메서드를 활용하고 싶다음 다음 링크 를 참고해주세요.
.isTrue() : 해당 결과가 참이면 통과합니다.
.isFalse() : 해당 결과가 거짓이면 통과합니다.
.isNull() : 해당 결과가 Null이면 통과합니다.
.isNotNull() : 해당 결과가 Null이 아니면 통과합니다.
assertThat(lotto.stream().allMatch(v -> v >= 1 && v <= 45)).isTrue();
// lotto 는 List형 변수이며 해당 요소가 모두 1 이상 45 이하이면 테스트 통과
Test Fail 메세지는 테스트 결과 메세지를 튜닝하는 기능을 갖고있습니다.
에러 메세지는 as 메서드로 지정할 수 있습니다. 단 , as 메서드는 assertions이 수행되기전에 사용해야합니다.
assertThat(500) // 500을 테스트 합니다.
.as("[테스트 : %d ] " , 500)
.isEqualTo(100); // 100 과 일치해야 합니다.
메세지 결과 : [테스트 : 500] expected:<100> but was:<33>
filtering assertions는 람다 또는 배열 , 링크드리스트에 적용할 수 있습니다.
해당 배열에 값이 들어있는지 필터링하는 기능입니다.
// 단순 리스트
assertThat(리스트).filteredOn(data -> data.equals("문자열1")).containsOnly("문자열2" , "문자열3");
// 클래스 리스트
assertThat(리스트).filteredOn(data -> data.getName().equals("문자열1")).containsOnly("문자열2" , "문자열3");
assertThat(list).filteredOn("age", notIn(22)).containsOnly(park, lee);
// 연쇄적
assertThat(members)
.filteredOn("type", ADMIN)
.filteredOn(m -> m.getAge() > 20)
.containsOnly(james);
filteredOn 메서드는 연쇄적으로 진행할 수 있습니다!!
배열 요소중에 "문자열1" 과 일치하는 값 중에서 "문자열2" 혹은 "문자열3"을 포함하고 있다면 테스트 성공이라는 의미를 갖고 있습니다. containsOnly
안에는 여러 문자열을 포함할 수 있습니다.
3번째에 filteredOn 메서드 두번째 인자로 notIn이 들어간것을 볼 수 있는데, 이것은 age 필드가 22가 아닌 객체들을 검증하는 것 입니다. 사용할 수 있는 함수는 다음과 같습니다.
리스트에서 특정 필드만 뽑아 테스트할 수 있습니다. 특정 필드는 클래스의 필드를 의미합니다.
assertThat(클래스 배열).extracting("필드 명").contains("문자열1" , "문자열2").doesNotContain("문자열3" , "문자열4");
클래스 리스트안에 '필드 명' 필드중에 "문자열1" 혹은 "문자열2" 를 포함하고 있으며 , "문자열3" , "문자열4" 를 포함하고 있지 않다면 성공합니다.
추가로 강하게 타입을 명시하고 싶으면 extracting("필드 명" , String.class) 로 클래스의 이름을 명시할 수 있습니다. 해당 "필드 명"이 문자열
이라는 것을 명시하는 것입니다.
여러 필드를 검사하고 싶은 경우 tuple를 사용해주세요.
assertThat(list).extracting("name", "age") // name 과 age 필드를 탐색
.contains(tuple("Kim", 22),
tuple("Park", 25),
tuple("Lee", 22),
tuple("Amy", 25),
tuple("Jack",22))
soft assertions을 사용하면 모든 assertions을 실행한 후 실패 내역만 확인할 수 있습니다.
이는 테스트 중 assertThat() 하나가 실패하면 해당 테스트 자체가 중단되는데, soft assertions을 사용하면 실패 내역만 볼 수 있습니다.
SoftAssertions softly = new SoftAssertions();
softly.assertThat(num1).as("num1 is %d", num1).isEqualTo(5);
softly.assertThat(num2).as("num2 is %d", num2).isEqualTo(6);
softly.assertThat(str).as("str is %s", str).isEqualTo("hi");
softly.assertAll();
한 테스트에 여러 테스트를 진행하여 실패한 테스트만 보여주며 모두 성공했을 시엔 통과합니다.
파일에 대한 테스트를 제공하며 해당 데이터가 파일인지 , 존재하는지 테스트를 할 수 있습니다.
File file = new File(fileName);
assertThat(file).exists().isFile().isRelative();
assertThat(contentOf(file))
.startsWith("You")
.contains("Know Nothing")
.endsWith("Jon Snow");
assertThatThrownBy() 메서드를 활용해 예외 처리를 할 수 있습니다.
AssertJ에서는 다음과 같은 함수가 대표적입니다. 각각의 함수는 해당 예외를 다룹니다.
assertThatNullPointerException
assertThatIllegalArgumentException
assertThatIllegalStateException
assertThatIOException
예시
assertThatNullPointerException().isThrownBy(() -> { 메서드 })
.withMessage("%s" , "메세지")
.withMessageContaining("nu");
assertThatExceptionOfType(IOException.class).isThrownBy(() -> { throw new IOException("boom!"); })
.withMessage("%s!", "boom")
.withMessageContaining("boom");
방법 2
assertThatThrownBy(() -> { throw new Exception("boom!"); }) .isInstanceOf(Exception.class) .hasMessageContaining("boom");
- assertThatThrownBy() : 예외가 발생하는 메서드
- isInstanceOf() : 발생한 예외 클래스
- hasMessageContaining() : 해당 메세지를 포함하고 있어야 합니다.
그 외에도 여러 메서드가 존재합니다. 이름이 직관적이니 설명을 굳이 하지 않아도 바로 사용할 수 있습니다.
// given
final LottoNumberGenerator lottoNumberGenerator = new LottoNumberGenerator();
final int price = 2000;
// when
final RuntimeException exception = assertThrows(RuntimeException.class, () -> lottoNumberGenerator.generate(price));
// then
assertThat(exception.getMessage()).isEqualTo("올바른 금액이 아닙니다.");
// 혹은 어떤 클래스 예외인지 확인 하려면
assertThat(thrown).isInstanceOf(UnauthorizedException.class).hasMessageContaining("Invalid access token");
다음처럼 RuntimeException
예외가 발생했을 때 when 단계에서 assertThrows() 를 활용해 변수를 리턴 받아 then 단계에서 assertThat() 메서드로 처리할 수 있습니다.
// WHEN
Throwable thrown = catchThrowable(() -> { throw new Exception("boom!"); });
// THEN
assertThat(thrown).doesNotThrowAnyException();
BDD 스타일은 Given , when , then 으로 이루어진 스타일로 조금 더 가독성 있는 코드를 작성할 수 있습니다.
자세한 내용은 하단 링크의 블로그에서 자세하게 다룹니다.
참고 블로그 1 : https://pjh3749.tistory.com/241
참고 블로그 2 : https://hseungyeon.tistory.com/328
참고 블로그 3 : https://steady-coding.tistory.com/351
참고 블로그 4 : https://sun-22.tistory.com/86