엔지니어로 가는 길

Spring boot @MockBean 알아보기 본문

프로그래밍/테스트

Spring boot @MockBean 알아보기

탐p슨 2022. 10. 22. 14:39
728x90

@MockBean

- Spring boot에서 제공하는 어노테이션으로 Spring 컨테이너에 mock을 추가하고 싶을 때 사용

    - Mock이란 객체를 흉내 낸 객체

- Junit 4) @RunWith(SpringRunner.class) 어노테이션이 붙은 테스트 클래스에서 사용

- Junit 5) @ExtendWith(SpringExtension.class) 어노테이션이 붙은 테스트 클래스에서 사용

- Mock은 타입 또는 빈 이름에 의해 등록 가능

    - 타입에 의해 등록될 때는 해당 타입의 빈이 하나 존재하는 경우 mock으로 대체

    - 이름에 의해 등록될 때는 해당 이름을 가진 빈이 존재할 경우 mock으로 대체

    - 두 경우 모두 빈이 존재하지 않는다면 mock을 추가

 

예시(출처: Spring boot docs)

 @RunWith(SpringRunner.class)
 public class ExampleTests {

     @MockBean
     private ExampleService service;

     @Autowired
     private UserOfService userOfService;

     @Test
     public void testUserOfService() {
         given(this.service.greet()).willReturn("Hello");
         String actual = this.userOfService.makeUse();
         assertEquals("Was: Hello", actual);
     }

     @Configuration
     @Import(UserOfService.class) // A @Component injected with ExampleService
     static class Config {
     }


 }

 

해석해보자.

 

UserOfService는 ExampleService에 의존한다

위의 테스트 코드에서 테스트하고자 하는 것은 UserOfService라는 클래스의 makeUse()라는 메소드이다. 이때 makeUse()라는 메소드는 ExampleService 클래스의 greet 메소드의 리턴 값을 사용하고 있기 때문에, makeUse() 메소드를 실행하기 위해서는 ExampleService 객체가 필요한 상황이다.

 

UserOfService를 테스트하기 위해 ExampleService는 mock으로 대체한다

하지만 UserOfService 클래스의 makeUse() 메소드가 잘 동작하는지를 보고 싶은 것뿐이므로 테스트에서는 ExampleService 객체가 꼭 필요하지는 않다. greet() 메소드는 리턴 값만 주면 이 테스트에서의 역할은 끝이다. 따라서 ExampleService의 mock을 만든 뒤, greet() 메소드가 호출이 되면 "Hello"라는 문자열이 리턴되도록 정해두었고, 이 상황에서 UserOfService의 makeUse() 메소드는 기대한 대로 동작하는지를 테스트하고 있다.

 

이로써 UserOfService의 테스트는 UserOfService에만 영향을 받게 되었다. ExampleService 클래스가 아직 존재하지 않는 상황에서도 UserOfService를 테스트할 수 있게 되었고, ExampleService의 빈을 만들지 않아도 되기 때문에 더 빠르게 테스트를 할 수 있다.

 

특정 빈이 테스트에서 사용되지는 않지만 필요한 경우

UserOfService라는 클래스가 ExampleService2에도 의존하는 경우를 가정해보자. makeUse()라는 메소드에서는 ExampleService2에 의존하지 않지만 다른 메소드에서 ExampleService2에 의존한다. 이렇게 되면 UserOfService 빈을 만들기 위해서는 ExampleService2 빈이 필요하다. @MockBean을 통해서 ExampleService2 mock을 생성할 수 있지만, ExampleService2 mock은 테스트에서 사용되지 않으므로 필드로 가지고 있는 것은 부자연스러워 보인다.

 

 @RunWith(SpringRunner.class)
 public class ExampleTests {

     @MockBean
     private ExampleService service;
     
     @MockBean
     private ExampleService2 service2; // 테스트에서 사용되지 않으나 UserOfService 빈 생성을 위해 필요

     @Autowired
     private UserOfService userOfService;

     @Test
     public void testUserOfService() {
         given(this.service.greet()).willReturn("Hello");
         String actual = this.userOfService.makeUse();
         assertEquals("Was: Hello", actual);
     }

     @Configuration
     @Import(UserOfService.class) // A @Component injected with ExampleService
     static class Config {
     }


 }

 

이런 경우는 @MockBean을 테스트 클래스 레벨에 붙여주는 게 더 좋을 것 같다.

 

 @RunWith(SpringRunner.class)
 @MockBean(ExampleService2.class)
 public class ExampleTests {

     @MockBean
     private ExampleService service;

     @Autowired
     private UserOfService userOfService;

     @Test
     public void testUserOfService() {
         given(this.service.greet()).willReturn("Hello");
         String actual = this.userOfService.makeUse();
         assertEquals("Was: Hello", actual);
     }

     @Configuration
     @Import(UserOfService.class) // A @Component injected with ExampleService
     static class Config {
     }


 }

 

위와 같이 수정하게 되면 사용하지도 않을 ExampleService2에 이름을 붙이지 않아도 되고, 테스트 코드에서 사용하지 않을 것이라는 게 더 명시적으로 보인다.

 

흔한 상황은 아니다. ExampleService2에 의존하는 메소드가 있다면 그 메소드에 대한 테스트를 작성할 때 필드에서 mock을 주입받아 사용해야 하기 때문이다.

 

회사에서 테스트 코드가 없는 복잡한 Service 클래스에 새로운 기능을 개발하거나 기존 기능을 수정할 때마다 테스트를 하나씩 추가하고 있는데, 이런 경우라면 당장 추가할 테스트를 위해 필요한 mock은 필드로 두고 나머지는 클래스 레벨로 분리하는 식으로 활용할 수 있다.

 

참고로 @MockBean은 @Repeatable이 붙은 어노테이션이기 때문에 빈 생성을 위해 필요한 @MockBean이 많은 경우 아래와 같이 사용할 수도 있다.

 

// ...
@MockBeans({
	@MockBean(ShoppingService.class),
	@MockBean(ShopRepository.class)
})
public class PurchaseControllerTest {
	// ...
}
// 코드블럭 출처: https://stackoverflow.com/questions/56132357/mockbeans-example-use

 

728x90
Comments