[JUnit5] JUnit5 AssertJ
in Development on Server, Junit5
#목차
- JUnit5 AssertJ
- Reference
JUnit5 AssertJ
AssertJ VS Assertions
JUnit 팀의 AssertJ 라이브러리를 사용 권장
“
JUnit Jupiter
에서 제공하는Assertions
기능만으로도 많은 테스트 시나리오에 충분하지만, 더 강력한 성능과 매처와 같은 추가 기능이 필요하거나 필요한 경우가 있습니다. 이러한 경우 JUnit 팀은AssertJ
,Hamcrest
,Truth
등과 같은 타사 어설션 라이브러리를 사용할 것을 권장합니다. 따라서 개발자는 원하는Assertions
라이브러리를 자유롭게 사용할 수 있습니다. - JUnit5 Doc”
AssertJ의 장점
- 메소드 체이닝을 지원하기 때문에 좀 더 깔끔하고 읽기 쉬운 테스트 코드를 작성할 수 있다.
- 개발자가 테스트를 하면서 필요하다고 상상할 수 있는 거의 모든 메소드를 제공한다.
라이브러리 의존성 설정
- SpringBoot를 사용하여 프로젝트를 생성하게되면
spring-boot-starter-test
라이브러리가 자동으로 들어가게 된다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
// ...
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
AssertJ VS Assertions 차이점
junit.jupiter의 Assertions
import static org.assertj.core.api.Assertions.assertThat;
class AssertionsTest {
@Test
void assertions_테스트() {
String str1 = "문자열";
String str2 = "문자열";
System.out.println("str1 = " + str1.hashCode());
System.out.println("str2 = " + str2.hashCode());
Assertions.assertEquals(str1, str2);
}
}
junit.jupiter의 Assertions 결과
assertj.core의 Assertions
import static org.assertj.core.api.Assertions.*;
public class AssertJTest {
@Test
void assertj_테스트() {
String str1 = "문자열";
String str2 = "문자열";
System.out.println("str1 = " + str1.hashCode());
System.out.println("str2 = " + str2.hashCode());
assertThat(str1).isEqualTo(str2);
}
}
assertj.core의 Assertions 결과
actual(실제 값)
을expexted(기대 값)
이 동일한지(isEqualTo()
) 확인하는 메서드이다.- 아직 까지는 크게 차이나보이진 않는다.
- 하지만
Collection Test
를 보면 크게 다르다는걸 느끼게 될 것이다.
Collection Test
class CollectionTest {
@Test
public void junit_assertions_테스트() {
ArrayList<Student> characters = new ArrayList<>();
Student spring = new Student("Spring");
Student jpa = new Student("Jpa");
Student kubernetes = new Student("Kubernetes");
characters.add(spring);
characters.add(jpa);
characters.add(kubernetes);
List<Student> expected = new ArrayList<>();
expected.add(spring);
expected.add(jpa);
List<Student> filteredList = characters.stream()
.filter((character) -> character.name.contains("p"))
.collect(Collectors.toList());
Assertions.assertEquals(expected,filteredList);
}
static class Student {
private String name;
public Student(String name) { this.name = name; }
}
}
class CollectionTest {
@Test
public void assertj_테스트() {
ArrayList<Student> characters = new ArrayList<>();
Student spring = new Student("Spring");
Student jpa = new Student("Jpa");
Student kubernetes = new Student("Kubernetes");
characters.add(spring);
characters.add(jpa);
characters.add(kubernetes);
assertThat(characters)
.filteredOn(character -> character.name.contains("p"))
.containsOnly(spring, jpa);
}
static class Student {
private String name;
public Student(String name) { this.name = name; }
}
}
- 위 두 테스트 코드를 보게 되면 차이가 많이난다.
AssertJ
를 통해filteredOn()
메서드의 람다식을 이용하여 배열character.name.contains("p")
를 이용하여p
가 들어간 것들을 찾는다containsOnly(spring, jpa)
를 이용하여spring
변수와jpa
변수에p
가 포함되어있으면 테스트 성공하게 된다.- 이러한 기능들 때문에
AssertJ
라이브러리를 많이 사용하게 된다.
Collection Test 결과
AssertJ 기본 검증 메서드
메서드 | 설명 |
---|---|
isEqualTo(값) | 검증대상과 동일한 값인지 비교한다. |
isSameAs(값) | 검증대상과 값을 == 비교한다. |
isNotNull() | 검증대상이 Not Null 인지 확인한다. |
isNull() | 검증대상이 Null 인지 확인한다. |
isNotEmpty() | 검증대상이 Not Empty 인지 확인한다. |
isEmpty() | 검증대상이 Empty 인지 확인한다. |
isIn() | 검증대상이 값 목록에 포함되어 있는지 검증한다. |
isNotIn() | 검증대상이 값 목록에 포함되어 있지 않는지 검증한다. |
isIn()
과isNotIn()
의 값 목록은가변 인자(변하는 인자의 개수)
로 주거나List
와 타입을 이용해서 전달한다.
import static org.assertj.core.api.Assertions.assertThat;
class JUnitTest {
@Test
void 기본_테스트_메서드() {
// isEqualTo()
String str1 = new String("hello");
String str2 = new String("hello");
assertThat(str1).isEqualTo(str2); // 비교 대상의 내용이 같은지 확인
// isSameAs()
String str3 = str1;
assertThat(str1).isSameAs(str3); // 같은 객체를 참조하고 있는지 확인
// isNotNull()
assertThat(str1).isNotNull(); // 객체가 null이 아닌지 확인
// isNull()
String str4 = null;
assertThat(str4).isNull(); // 객체가 null인지 확인
// isNotEmpty() and isEmpty()
List<String> emptyList = new ArrayList<>();
List<String> nonEmptyList = Arrays.asList("apple", "banana");
assertThat(emptyList).isEmpty(); // 비어있는지 확인
assertThat(nonEmptyList).isNotEmpty(); // 비어있지 않은지 확인
// isIn() and isNotIn()
String fruit1 = "apple";
String fruit2 = "orange";
assertThat(fruit1).isIn(nonEmptyList); // "apple"이 리스트에 포함되어 있는지 확인
assertThat(fruit2).isNotIn(nonEmptyList); // "orange"가 리스트에 포함되어 있지 않은지 확인
}
}
AssertJ 기본 검증 메서드 결과
AssertJ isIn(), IsNotIn 메서드 결과
AssertJ 문자열 검증 메서드
메서드 | 설명 |
---|---|
contains(값) | 검증대상에 (값)이 포함되어있는지 확인한다. |
containsOnlyOnce(값) | 검증대상에 (값)이 딱 한 번만 포함되어있는지 확인한다. |
containsOnlyDigits() | 숫자만 포함하는지 검증한다. |
containsWhitespaces() | 공백 문자를 포함하고 있는지 검증한다. |
containsOnlyWhitespaces() | 공백 문자만 포함하는지 검증한다. |
doesNotContain(값) | 검증대상의 공백 문자만 포함하는지 검증한다. |
doesNotContainAnyWhitespaces() | 검증대상의 공백 문자를 포함하고 있지 않은지를 검증한다. |
doesNotContainOnlyWhitespaces() | 검증대상의 공백 문자만 포함하고 있지 않은지를 검증한다. |
doesNotContainPattern(패턴) | 검증대상의 정규 표현식에 일치하는 문자를 포함하고 있지 않은지를 검증한다. |
startsWith(값) | 검증대상의 시작 값이 (값)과 동일한지 비교한다. |
endsWith(값) | 검증대상의 마지막 값이 (값)과 동일한지 비교한다. |
doesNotStartWith(값) | 검증대상의 (값)이 지정한 문자열로 시작하지 않는지를 검증한다. |
doesNotEndWith(값) | 검증대상의 (값)이 지정한 문자열로 끝나지 않는지를 검증한다. |
import static org.assertj.core.api.Assertions.*;
public class AssertJTest {
@Test
void 문자열_테스트() {
assertThat("Hello, world! Nice to meet you.") // 주어진 "Hello, world! Nice to meet you."라는 문자열은
.isNotEmpty() // 비어있지 않고
.contains("Nice") // "Nice"를 포함하고
.contains("world") // "world"도 포함하고
.doesNotContain("ZZZ") // "ZZZ"는 포함하지 않으며
.startsWith("Hell") // "Hell"로 시작하고
.endsWith("u.") // "u."로 끝나며
.isEqualTo("Hello, world! Nice to meet you."); // "Hello, world! Nice to meet you."과 일치
}
}
AssertJ 문자열 검증 메서드 결과
AssertJ 숫자 검증 메서드
메서드 | 설명 |
---|---|
isPositive() | 검증대상이 양수인지 확인한다. |
isNotPositive() | 검증대상이 양수가 아닌지 확인한다. |
isNegative() | 검증대상이 음수인지 확인한다. |
isNotNegative() | 검증대상이 음수가 아닌지 확인한다. |
isZero() | 검증대상이 0 인지 확인한다. |
isNotZero() | 검증대상이 0 이 아닌지 확인한다. |
isOne() | 검증대상이 1 인지 확인한다. |
isGraterThan(값) | 검증대상이 값을 초과한지 확인한다. |
isLessThan(값) | 검증대상이 값보다 미만인지 확인한다. |
isGraterThanOrEqualTo(값) | 검증대상이 값 이상인지 확인한다. |
isLessThanOrEqualTo(값) | 검증대상이 값 이하인지 확인한다. |
isBetween(값1, 값2) | 값1 과 값2 사이에 포함되는지 검증한다. |
import static org.assertj.core.api.Assertions.*;
public class AssertJTest {
@Test
void 숫자_테스트() {
assertThat(3.14d) // 주어진 3.14라는 숫자는
.isPositive() // 양수이고
.isGreaterThan(3) // 3보다 크며
.isLessThan(4) // 4보다 작습니다
.isEqualTo(3, offset(1d)) // 오프셋 1 기준으로 3과 같고
.isEqualTo(3.1, offset(0.1d)) // 오프셋 0.1 기준으로 3.1과 같으며
.isEqualTo(3.14); // 오프셋 없이는 3.14와 같습니다
}
}
AssertJ 숫자 검증 메서드 결과
AssertJ 날짜 검증 메서드
메서드 | 설명 |
---|---|
isBefore(비교 값) | 비교 값 보다 이전인지 검증한다. 실제 값 < 비교 값 |
isBeforeOrEqualTo(비교 값) | 비교 값 보다 이전이거나 같은지 검증한다. 실제 값 <= 비교 값 |
isAfter(비교 값) | 비교 값 보다 이후인지 검증한다. 실제 값 > 비교 값 |
isAfterOrEqualTo(비교 값) | 비교 값 보다 이후이거나 같은지 검증한다. 실제 값 >= 비교 값 |
AssertJ 날짜 검증 메서드 결과
as - Fail Message
as(String description, Object... args)
를 사용하여 테스트 코드의 실패 메시지를 설정할 수 있다.as
는 검증 문보다 앞에 작성해야 하며, 그렇지 않을 경우 검증 문 이후 호출이 중단됨으로 무시된다.
import static org.assertj.core.api.Assertions.*;
class FailMessageTest {
@Test
void fail_message_테스트() {
String str = "JUnit";
assertThat(str)
// as는 검증 문보다 앞에 작성해야 하며, 그렇지 않을 경우 검증 문 이후 호출이 중단됨으로 무시된다.
.as("기대값(Expected) AssertJ와 실제값(Actual) %s이 일치하지 않습니다.", str)
.isEqualTo("AssertJ");
}
}
as - Fail Message 결과
filteredOn 메서드
import static org.assertj.core.api.Assertions.*;
class FilteredOnTest {
public Member member1, member2, member3;
public List<Member> members;
static class Member {
private String name;
private int age;
private MemberRole role;
public Member(String name, int age, MemberRole role) {
this.name = name;
this.age = age;
this.role = role;
}
public String getName() { return name; }
public int getAge() {return age; }
public MemberRole getRole() { return role; }
}
enum MemberRole { ADMIN, BASIC, VIP }
@BeforeEach
public void createMember() {
member1 = new Member("Kim", 20, MemberRole.ADMIN);
member2 = new Member("Ahn", 20, MemberRole.BASIC);
member3 = new Member("Park", 21, MemberRole.VIP);
members = Lists.list(member1, member2, member3);
}
@Test
public void 체이닝_람다를_사용한_필터_테스트() {
assertThat(members) // members 컬렉션에서
.filteredOn(member -> member.getAge() >= 20) // age >= 20 인 객체만 필터링
.filteredOn(member -> !member.getName().equals("Kim")) // name이 "Kim"과 같지 않은 객체만 필터링
.filteredOn("role", in(MemberRole.VIP)) // "role"이 MemberRole.VIP인 객체를 필터링
.containsOnly(member3) // 그 객체가 오직 member3 이며,
.isNotEmpty(); // 비어있지 않다.
}
}
- 위 테스트 처럼 체이닝과 람다를 활용하여 더욱 편리하게
filteredOn()
메서드를 사용할 수 있다.
AssertJ filteredOn 메서드 결과
Extracting 메서드
extracting()
메서드의 파라미터로필드명
을 입력하거나,메서드 레퍼런스
도 표현이 가능하다.
import static org.assertj.core.api.Assertions.*;
class FilteredOnTest {
public Member member1, member2, member3;
public List<Member> members;
class Member {
private String name;
private int age;
private MemberRole role;
public Member(String name, int age, MemberRole role) {
this.name = name;
this.age = age;
this.role = role;
}
public String getName() { return name; }
public int getAge() {return age; }
public MemberRole getRole() { return role; }
}
enum MemberRole { ADMIN, BASIC, VIP }
@BeforeEach
public void createMember() {
member1 = new Member("Kim", 20, MemberRole.ADMIN);
member2 = new Member("Ahn", 20, MemberRole.BASIC);
member3 = new Member("Park", 21, MemberRole.VIP);
members = Lists.list(member1, member2, member3);
}
@Test
public void 메서드_레퍼런스를_이용한_필터_테스트() {
assertThat(members) // members 컬렉션에서
.filteredOn(
member -> member.getAge() >= 20 // member 객체의 age가 >= 20 인 객체만 필터링
&& !member.getName().equals("Kim") // member 객체의 name이 "Kim"과 같지 않은 객체만 필터링
&& member.getRole().equals(MemberRole.VIP) // member 객체의 role이 MemberRole.VIP인 객체를 필터링
)
.extracting(
Member::getName, // member -> member.getName() => Member 클래스 객체의 getName() 메서드를 참조
Member::getAge, // member -> member.getAge()
Member::getRole // member -> member.getRole()
)
.containsExactly(tuple("Park", 21, MemberRole.VIP)); // containsExactly(): 순서 원소의 개수와 값이 일치해야 한다.
}
}
- 위
@BeforeEach
를 통해@Test
어노테이션이 붙은 테스트 메서드를 실행하기 전createMember()
메서드를 실행하여Member
객체들을 생성한다. - 이후
extracting()
메서드에서람다 표현식
을 더 간단하게 표현하는 방법인메서드 레퍼런스
를 이용하여Member
객체의 메서드를 참조(Method Reference
)를 이용하여.filteredOn()
을 구현한다. - 마지막
.containsExactly()
메서드에 인자로tuple()
을 사용하여 각기 다른 인자값들을 받아 테스트 하게 되는데, 이때.containsExactly()
는 순서 원소의 개수와 값이 모두 일치해야 한다.
Extracting 메서드 결과
assertThatThrownBy() : 예외처리
assertThatThrownBy() : 예외처리 에러1
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class AssertThrowsTest {
@Test
void assertThrows_테스트() {
// given
String input = "abc";
// when, then : 입력값 범위 밖일 경우 StringIndexOutOfBoundsException 발생
assertThatThrownBy(() -> input.charAt(input.length()))
.isInstanceOf(StringIndexOutOfBoundsException.class)
.hasMessageContaining("String index out of length");
}
}
- 에러 메세지의 값이
"String index out of range"
가 포함되어 있어야 하는 테스트이지만"String index out of length"
여서 테스트가 실패하게 된다.
assertThatThrownBy() - 예외처리 에러1 결과
assertThatThrownBy() - 예외처리 에러2
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class AssertThrowsTest {
@Test
void assertThrows_테스트() {
// given
String input = "abc";
// when, then : 입력값 범위 밖일 경우 StringIndexOutOfBoundsException 발생
assertThatThrownBy(() -> input.charAt(input.length()))
.isInstanceOf(StringIndexOutOfBoundsException.class)
.hasMessageContaining(String.valueOf(4));
}
}
- 에러 메세지의 값이
"String index out of range: 3"
가 포함되어 있어야 하는 테스트이지만"4"
여서 테스트가 실패하게 된다.
assertThatThrownBy() - 예외처리 에러2 결과
assertThatThrownBy() - 예외처리 성공
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class AssertThrowsTest {
@Test
void assertThrows_테스트() {
// given
String input = "abc";
// when, then : 입력값 범위 밖일 경우 StringIndexOutOfBoundsException 발생
assertThatThrownBy(() -> input.charAt(input.length()))
.isInstanceOf(StringIndexOutOfBoundsException.class)
.hasMessageContaining("String index out of range")
.hasMessageContaining(String.valueOf(input.length()));
}
}
- 에러 메세지의 값이
"String index out of range: 3"
가 포함되어야하는 값에 각각"String index out of range"
과"3"
이 포함되어 테스트가 성공하게 된다.
assertThatThrownBy() - 예외처리 결과
Back to [JUnit5] JUnit5