🌱 JAVA & SPRING/Spring

[spring-test] JUnit5 Test 정리

ming412 2023. 11. 24. 12:48

JUnit5의 μž₯단점

μž₯점

  • JUnit5λŠ” JUnit4의 λ¬Έμ œμ μ„ λ³΄μ™„ν•˜κ³  μƒˆλ‘œμš΄ κΈ°λŠ₯을 μΆ”κ°€ν–ˆμŠ΅λ‹ˆλ‹€.
  • ν…ŒμŠ€νŠΈ μΈμŠ€ν„΄μŠ€ 라이프사이클을 μ§€μ›ν•˜κ³ , 동적 ν…ŒμŠ€νŠΈλ₯Ό μ§€μ›ν•˜λ©°, ν…ŒμŠ€νŠΈ νŒŒλΌλ―Έν„°ν™” κΈ°λŠ₯도 μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
  • λ˜ν•œ λͺ¨λ“ˆν™”κ°€ κ°€λŠ₯ν•˜μ—¬ ν•„μš”ν•œ λͺ¨λ“ˆλ§Œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

- λ‹¨μ 

  • JUnit5λŠ” 아직 λ§Žμ€ Java κ°œλ°œμžλ“€μ΄ μ΅μˆ™ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. λ˜ν•œ IDE 지원이 JUnit4에 λΉ„ν•΄ λ―Έν‘ν•œ κ²½μš°λ„ μžˆμŠ΅λ‹ˆλ‹€.
  • JUnit5κ°€ JUnit4에 λΉ„ν•΄ λ§Žμ€ μƒˆλ‘œμš΄ κΈ°λŠ₯을 μ œκ³΅ν•˜λ―€λ‘œ μ΅œκ·Όμ—λŠ” 더 많이 μ‚¬μš©λ˜κ³  μžˆμŠ΅λ‹ˆλ‹€.
  • κ·ΈλŸ¬λ‚˜ 일뢀 ν”„λ‘œμ νŠΈμ—μ„œλŠ” 아직 JUnit4λ₯Ό 계속 μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

 

μ£Όμš” μ–΄λ…Έν…Œμ΄μ…˜ 정리

@ExtendWith()

JUnit 5의 μ–΄λ…Έν…Œμ΄μ…˜μœΌλ‘œ, JUnit 4의 `@RunWith` 을 λŒ€μ²΄ν•œλ‹€. `@RunWith` μ—μ„œ Runner 의 κ΅¬ν˜„μ²΄λ₯Ό 인자둜 λ°›μ•˜λ˜ κ²ƒμ²˜λŸΌ, μ—¬κΈ°μ„œλŠ” Extension μΈν„°νŽ˜μ΄μŠ€μ˜ κ΅¬ν˜„μ²΄λ₯Ό 인자둜 λ°›λŠ”λ‹€. Junit 5 Testμ—μ„œ μ‚¬μš©ν•  κΈ°λŠ₯을 ν™•μž₯ν• λ•Œ μ‚¬μš©λœλ‹€.


μ΄λ•Œ μ˜΅μ…˜μœΌλ‘œ `@ExtendWith (SpringExtension.class)`와 `@ExtendWith (MockitoExtension.class)`이 λ§Žμ΄μ“°μΈλ‹€.

 

@ExtendWith (SpringExtension.class) - Spring MVC ν…ŒμŠ€νŠΈ

SpringExtension은 Spring ν…ŒμŠ€νŠΈλ₯Ό μ‰½κ²Œν•  수 μžˆλ„λ‘ λ„μ™€μ£ΌλŠ” Extensionμž…λ‹ˆλ‹€. 이λ₯Ό μ΄μš©ν•˜λ©΄ Spring ν…ŒμŠ€νŠΈμ— μ‚¬μš©ν•  ApplicationContextλ₯Ό μ‰½κ²Œ 생성할 수 μžˆμŠ΅λ‹ˆλ‹€.

(πŸ”— SpringExtension은 ApplicationContextλ₯Ό μ–΄λ–»κ²Œ λ§Œλ“€κΉŒ?)

 

@SpringBootTest, @DataJpaTest, @WebMvcTest, @JdbcTest λ“±μ˜ μ–΄λ…Έν…Œμ΄μ…˜μ€ `@ExtendWith (SpringExtension.class)`을 이미 ν¬ν•¨ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

 

@ExtendWith (MockitoExtension.class)

Mockito와 JUnit 5λ₯Ό ν†΅ν•©ν•˜μ—¬ λͺ© 객체(Mock Objects)λ₯Ό μ‰½κ²Œ μƒμ„±ν•˜κ³  μ‚¬μš©ν•  수 μžˆλ„λ‘ λ„μ™€μ£ΌλŠ” μ–΄λ…Έν…Œμ΄μ…˜μž…λ‹ˆλ‹€. 이 μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜λ©΄ ν…ŒμŠ€νŠΈ ν΄λž˜μŠ€μ—μ„œ Mockito와 ν•¨κ»˜ λͺ© 객체λ₯Ό μ΄ˆκΈ°ν™”ν•˜κ³  μ£Όμž…ν•  수 μžˆμŠ΅λ‹ˆλ‹€. (`@Mock` μ–΄λ…Έν…Œμ΄μ…˜ ν•„λ“œμ˜ μ΄ˆκΈ°ν™”)

 

`@ExtendWith(MockitoExtension.class)`λ₯Ό μ‚¬μš©ν•˜λ©΄ `@Mock` μ–΄λ…Έν…Œμ΄μ…˜μ„ ν•„λ“œμ— μ μš©ν•˜μ—¬ λͺ© 객체λ₯Ό μƒμ„±ν•˜κ³ , `@InjectMocks` μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜μ—¬ λͺ© 객체λ₯Ό μ£Όμž…ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό 톡해 μ˜μ‘΄μ„±μ„ κ°€μ§„ μ»΄ν¬λ„ŒνŠΈλ₯Ό μ‰½κ²Œ λͺ© 객체둜 λŒ€μ²΄ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

@ExtendWith(MockitoExtension.class)
public class MyServiceTest {

    @InjectMocks
    private MyService myService;

    @Mock
    private MyRepository myRepository;

    // ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„±
}

 

λ˜ν•œ, μ•„λž˜ 두 κ°€μ§€ 방법은 λ™μΌν•˜κ²Œ λ™μž‘ν•©λ‹ˆλ‹€.

// 방법 1
@ExtendWith(MockitoExtension.class)
public class JUnit5TestClass {...}

// 방법 2
public class JUnit5TestClass {
	@BeforeEach
    void setUp(){
        //μ€‘μš”! : base class λ˜λŠ” test runnerκ°€ ν•„μš”ν•©λ‹ˆλ‹€. μ—¬κΈ°μ„œλŠ” MockTestλ₯Ό λ„£μ–΄μ€λ‹ˆλ‹€.
        //μ—­ν•  : @Mock μ–΄λ…Έν…Œμ΄μ…˜ ν•„λ“œμ˜ μ΄ˆκΈ°ν™”
        MockitoAnnotations.openMocks(this);
    }
}

 

@SpringBootTest() - Spring 톡합 ν…ŒμŠ€νŠΈ

`@SpringBootTest` μ–΄λ…Έν…Œμ΄μ…˜μ€ JUnit 4와 JUnit 5에 κ³΅ν†΅μž…λ‹ˆλ‹€. 차이점은, JUnit5의 @SpringBootTestμ—λŠ” `@ExtendWith(SpringExtension.class)`κ°€ μ„ μ–Έλ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

 

`@SpringBootTest` μ–΄λ…Έν…Œμ΄μ…˜μ΄ λΆ™μ–΄μžˆλŠ” ν…ŒμŠ€νŠΈ ν΄λž˜μŠ€κ°€ μ‹€ν–‰λ˜λ©΄, `@SpringBootApplication` μ–΄λ…Έν…Œμ΄μ…˜μ„ μ°Ύμ•„ λͺ¨λ“  λΉˆμ„ λ‘œλ“œν•©λ‹ˆλ‹€. 덕뢄에 μ‹€μ œ μ½”λ“œμ²˜λŸΌ μ˜μ‘΄μ„±μ„ μ£Όμž…ν•˜μ—¬ μ‚¬μš© κ°€λŠ₯ν•˜λ‚˜ ν…ŒμŠ€νŠΈκ°€ 많이 λ¬΄κ±°μ›Œμ§€κ²Œ λœλ‹€.

 

λ˜ν•œ μ‹€μ œ λΉˆμ„ μ£Όμž…λ°›μ•„ ν…ŒμŠ€νŠΈν•˜κ²Œ 됨으둜써 데이터λ₯Ό μ‚½μž…ν•˜κ±°λ‚˜ μ‚­μ œν•˜λ©΄ μ‹€μ œ λ°μ΄ν„°μ˜ λ³€ν™”κ°€ 생길 수 μžˆλ‹€. μ˜λ„ν•œκ²Œ μ•„λ‹ˆλΌλ©΄ κΌ­ `@Transactional`을 μ΄μš©ν•˜μ—¬ rollback μ²˜λ¦¬ν•˜λ„λ‘ ν•œλ‹€.

@SpringBootTest
public class JUnit5TestClass {
	// μ˜μ‘΄μ„± μ£Όμž…μ€ @AutoWiredλ₯Ό μ΄μš©ν•œλ‹€.
    @AutoWired
    TestService testservice;
}

 

일반적인 μ˜μ‘΄μ„± μ£Όμž… 방법은 μ•„λž˜ 4κ°€μ§€λ₯Ό μ‚¬μš©ν•œλ‹€.
1. μƒμ„±μžλ₯Ό 톡해 μ˜μ‘΄μ„± μ£Όμž…
2. ν•„λ“œλ₯Ό ν†΅ν•΄μ„œ μ˜μ‘΄μ„± μ£Όμž…
3. setterλ₯Ό ν†΅ν•΄μ„œ μ˜μ‘΄μ„± μ£Όμž…
4. lombok을 μ΄μš©ν•œ final ν•„λ“œμ— μ˜μ‘΄μ„± μ£Όμž…

Junitμ—μ„œ μ˜μ‘΄μ„± μ£Όμž…μ„ ν•˜κΈ° μœ„ν•΄ λ‹€μ–‘ν•œ 방법을 μ‹œλ„ν•˜κ² μ§€λ§Œ κ°€μž₯ κ°„λ‹¨ν•œ 방법은 `@AutoWired`λ₯Ό μ΄μš©ν•˜λŠ” 것이닀. μƒμ„±μžλ‚˜ lombok 방식을 μ‚¬μš©ν•˜κ²Œ 되면 μ—λŸ¬κ°€ λ°œμƒν•  것이닀.

μ—λŸ¬κ°€ λ°œμƒν•˜λŠ” 원인은 JUnit 5κ°€ DIλ₯Ό 슀슀둜 μ§€μ›ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€. λ˜ν•œ DIλ₯Ό μ§€μ›ν•˜λŠ” νƒ€μž…μ΄ μ •ν•΄μ Έμžˆλ‹€.
JUnit 5μ—μ„œ μƒμ„±μžλ‚˜ lombok을 μ΄μš©ν•  수 μ—†λŠ” μ΄μœ λŠ”, JUnit이 μƒμ„±μžμ— μ˜μ‘΄μ„±μ„ μ£Όμž…ν•˜λ €κ³  λ¨Όμ € κ°œμž…ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.
 
πŸ”— [JUnit] Spring Boot ν…ŒμŠ€νŠΈν•˜κΈ° (Migration from JUnit4 to Junit5)

 

πŸ€” Spring MVC ν…ŒμŠ€νŠΈ vs Spring 톡합 ν…ŒμŠ€νŠΈ

Spring MVC ν…ŒμŠ€νŠΈ
Spring MVC ν…ŒμŠ€νŠΈλŠ” 주둜 μ›Ή 계측(controller)을 ν…ŒμŠ€νŠΈν•˜λŠ” 데 μ‚¬μš©λ©λ‹ˆλ‹€.주둜 MockMvcλ₯Ό μ‚¬μš©ν•˜μ—¬ 컨트둀러의 λ™μž‘ 및 μ›Ή μš”μ²­-응닡 μ£ΌκΈ°λ₯Ό ν…ŒμŠ€νŠΈν•©λ‹ˆλ‹€.ν…ŒμŠ€νŠΈ μ‹œμ—λŠ” μ‹€μ œ μ›Ή μ„œλ²„λ‚˜ λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠμœΌλ©°, 주둜 μŠ€ν”„λ§μ˜ @WebMvcTest μ–΄λ…Έν…Œμ΄μ…˜κ³Ό ν•¨κ»˜ μ‚¬μš©λ©λ‹ˆλ‹€.ν…ŒμŠ€νŠΈ λŒ€μƒμ€ μ»¨νŠΈλ‘€λŸ¬μ™€ κ΄€λ ¨λœ 둜직이며, μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ λ‹€λ₯Έ λΆ€λΆ„(μ„œλΉ„μŠ€, 리포지토리 λ“±)은 λͺ© 객체(Mock)λ₯Ό μ‚¬μš©ν•˜μ—¬ κ°€μ§œ 객체둜 λŒ€μ²΄ν•©λ‹ˆλ‹€.μ΄λŸ¬ν•œ ν…ŒμŠ€νŠΈλŠ” λΉ λ₯΄κ²Œ μ‹€ν–‰λ˜λ©°, μ›Ή 계측에 μ΄ˆμ μ„ 맞μΆ₯λ‹ˆλ‹€.

Spring 톡합 ν…ŒμŠ€νŠΈ
Spring 톡합 ν…ŒμŠ€νŠΈλŠ” μŠ€ν”„λ§ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μ—¬λŸ¬ ꡬ성 μš”μ†Œ(컨트둀러, μ„œλΉ„μŠ€, 리포지토리 λ“±) κ°„μ˜ μƒν˜Έ μž‘μš©μ„ ν…ŒμŠ€νŠΈν•˜λŠ” 데 μ‚¬μš©λ©λ‹ˆλ‹€.μ‹€μ œλ‘œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ»¨ν…μŠ€νŠΈλ₯Ό λ‘œλ“œν•˜κ³ , μ‹€μ œ λ°μ΄ν„°λ² μ΄μŠ€μ™€ ν†΅ν•©ν•˜μ—¬ λ™μž‘ν•©λ‹ˆλ‹€.주둜 @SpringBootTest μ–΄λ…Έν…Œμ΄μ…˜κ³Ό ν•¨κ»˜ μ‚¬μš©λ˜λ©°, μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 전체λ₯Ό μ΄ˆκΈ°ν™”ν•˜κ³  μ‹€μ œ λΉˆμ„ μ‚¬μš©ν•©λ‹ˆλ‹€.μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μ—¬λŸ¬ 계측 κ°„ μƒν˜Έ μž‘μš© 및 톡합 λ™μž‘μ„ κ²€μ¦ν•˜λ©°, 더 λ§Žμ€ μ‹œκ°„κ³Ό λ¦¬μ†ŒμŠ€λ₯Ό ν•„μš”λ‘œ ν•©λ‹ˆλ‹€.

μ›Ή 계측(controller)에 μ΄ˆμ μ„ λ‘” λΉ λ₯Έ ν…ŒμŠ€νŠΈλ₯Ό μ›ν•œλ‹€λ©΄ Spring MVC ν…ŒμŠ€νŠΈλ₯Ό κ³ λ €ν•˜κ³ , μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ „μ²΄μ˜ μƒν˜Έ μž‘μš© 및 톡합 λ™μž‘μ„ κ²€μ¦ν•˜λ €λ©΄ Spring 톡합 ν…ŒμŠ€νŠΈλ₯Ό κ³ λ €ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

@WebMvcTest()

컨트둀러λ₯Ό ν…ŒμŠ€νŠΈν•  λ•Œ 주둜 μ΄μš©λ©λ‹ˆλ‹€. μ£Όμ˜ν•  점은 @Service, @Component, @Repository 등은 μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€.

κ·Έ μ΄μœ λŠ” 단일 클래슀의 ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ν•˜κΈ° λ•Œλ¬ΈμΈλ°, λ§Œμ•½ μ‚¬μš©ν•˜κ³  μ‹Άλ‹€λ©΄ @MockBean을 톡해 각자 객체λ₯Ό λ§Œλ“€μ–΄ μ£Όμ–΄μ•Ό ν•©λ‹ˆλ‹€.

 

(예λ₯Ό λ“€μ–΄, `@WebMvcTest(DiaryController.class)`둜 μ§€μ •ν–ˆλ‹€λ©΄, FriendshipController와 κ΄€λ ¨λœ μŠ€ν”„λ§ λΉˆλ“€λ§Œ λ‘œλ“œν•˜λ―€λ‘œ λ‹€λ₯Έ μ»¨νŠΈλ‘€λŸ¬λ‚˜ μ„œλΉ„μŠ€μ™€ κ΄€λ ¨λœ λΉˆλ“€μ€ λ‘œλ“œλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.)

 

@DataJpaTest

 

@Test

Test μ–΄λ…Έν…Œμ΄μ…˜μ€ JUnit 4와 JUnit 5에 κ³΅ν†΅μž…λ‹ˆλ‹€. μ–΄λ…Έν…Œμ΄μ…˜μ— μžˆλŠ” λ©”μ„œλ“œλŠ” 클래슀의 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό λ‚˜νƒ€λƒ…λ‹ˆλ‹€.

 

@MockBean

@WebMvcTestλ₯Ό μ΄μš©ν•œ ν…ŒμŠ€νŠΈμ—μ„œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Bean μ»¨ν…Œμ΄λ„ˆμ— 객체(Service)κ°€ μžˆμ–΄μ•Ό λ‹€λ₯Έ 객체(Controller)와 ν˜‘λ ₯ν•  수 μžˆλŠ”λ°, 객체λ₯Ό λ§Œλ“€ 수 μ—†λŠ” 경우 @MockBean을 μ‚¬μš©ν•©λ‹ˆλ‹€.

@WebMvcTest(RegionController.class)
public class JUnit5TestClass {
   @Autowired
   private MockMvc mvc;

   @MockBean
   private RegionService regionService;

    @Test
    void getRegions() throws Exception {
        List<Region> regions = new ArrayList<>();
        regions.add(
        	Region.builder()
                .name("Seoul")
                .build()
        );

        given(regionService.getRegions()).willReturn(regions);

        mvc.perform(get("/regions"))
        	.andExpect(status().isOk());
    }
}

 

@Mock

mock 객체λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.

 

@InjectMocks

@Mock둜 주석이 달린 mock 객체λ₯Ό @InjectMocks둜 주석이 달린 객체에 μ£Όμž…μ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€. μ‹€λ¬΄μ—μ„œλŠ” Service ν…ŒμŠ€νŠΈ mock 객체에 DAO mock 객체λ₯Ό μ£Όμž…μ‹œμΌœ μ‚¬μš©ν•œλ‹€.

public class MembmerServiceTest{
    @Mock
    MemberDao memberDao;

    @InjectMocks
    MemberService memberService;

    @Test
    public void test(){
            when(memberDao.getMemberCount()).thenReturn(0);

            Member member = new Member("corn", 25);
            assertThat(memberService.createMember(member), is(member));
    }
}

 

@SpyBean

 

@BeforeEach / @AfterEach

- ν…ŒμŠ€νŠΈ 클래슀의 각 ν…ŒμŠ€νŠΈ λ©”μ„œλ“œλ₯Ό μ‹€ν–‰ν•˜κΈ° 전에 μ‹€ν–‰λ©λ‹ˆλ‹€.

- 각 ν…ŒμŠ€νŠΈλ₯Ό μ‹œμž‘ν•˜κΈ° 직전/후에 λ¦¬μ†ŒμŠ€ λ˜λŠ” ν…ŒμŠ€νŠΈ 데이터λ₯Ό μ„€μ •ν•˜λ €λŠ” 경우 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

- 예λ₯Ό λ“€μ–΄, JUnit ν…ŒμŠ€νŠΈ ν΄λž˜μŠ€μ— 5개의 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€κ°€ μžˆλŠ” 경우 @BeforeEach / @AfterEach둜 주석이 달린 λ©”μ„œλ“œλŠ” 각 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€κ°€ μ‹€ν–‰λ˜κΈ° μ „/후에 5번 μ‹€ν–‰λ©λ‹ˆλ‹€.

 

@BeforeAll / @AfterAll

- ν…ŒμŠ€νŠΈ 클래슀의 λͺ¨λ“  ν…ŒμŠ€νŠΈ λ©”μ„œλ“œκ°€ μ‹€ν–‰λ˜κΈ° μ „/후에 μ‹€ν–‰λ©λ‹ˆλ‹€.

- 클래슀 μˆ˜μ€€μ—μ„œ 데이터λ₯Ό ν…ŒμŠ€νŠΈν•˜λ €λŠ” κ²½μš°μ— μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

- 예λ₯Ό λ“€μ–΄, JUnit ν…ŒμŠ€νŠΈ ν΄λž˜μŠ€μ— 5개의 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€κ°€ μžˆλŠ” 경우 @BeforeAll / @AfterAll둜 주석이 달린 μΌ€μ„œλ“œλŠ” ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€κ°€ μ‹œμž‘λ˜κΈ° μ „/후에 ν…ŒμŠ€νŠΈ 클래슀 λ‹Ή ν•œ 번 μ‹€ν–‰λ©λ‹ˆλ‹€.

 

@Disabled

νŠΉμ • ν…ŒμŠ€νŠΈ λ©”μ„œλ“œλ₯Ό μ‹€ν–‰ν•˜μ§€ μ•Šλ„λ‘ μ§€μ •ν•©λ‹ˆλ‹€. μΌμ‹œμ μœΌλ‘œ ν…ŒμŠ€νŠΈλ₯Ό λΉ„ν™œμ„±ν™”ν•˜κ³ μž ν•  λ•Œ μ‚¬μš©λ©λ‹ˆλ‹€.

 

πŸ”— μ°Έκ³ 

Spring JUnit5 Test정리

[Mockκ³Ό Mockito] @Mock @MockBean

[Spring Boot] Controller λ‹¨μœ„ ν…ŒμŠ€νŠΈ (@WebMvcTest, MockMvc)