발단
나는 최근에 소셜 로그인을 구현하고, 그에 따른 테스트 코드를 JUnit5를 사용해 작성하고 있었다.
그 중에서도 @WebMvcTest를 통해 컨트롤러 단위 테스트를 작성하고 있었다.
테스트 코드
테스트 코드 내용은 간단하다.
@WebMvcTest(OAuthController.class)
class OAuthControllerTest {
@Autowired
protected ObjectMapper objectMapper;
@Autowired
protected MockMvc mockMvc;
@MockBean
protected OAuthRedirectUriProviderComposite redirectUriProvider;
@BeforeEach
void setup(WebApplicationContext context) {
MockMvcBuilders.webAppContextSetup(context)
.alwaysDo(print())
.build();
}
@Test
void 플랫폼에_맞는_AuthorizationCode를_받기위한_요청_URI를_반환한다() throws Exception {
// given
// 나중에 Fixture 변경
Platform platform = Platform.KAKAO;
URI expectUri = UriComponentsBuilder.fromUri(URI.create("www.provider.com" + "/oauth/authorize"))
.queryParam("client_id", "test_id")
.queryParam("client_secret", "test_secret")
.queryParam("redirect_uri", "test_front_redirect_uri")
.queryParam("response_type", "code")
.build().toUri();
given(redirectUriProvider.generateRedirectUri(platform)).willReturn(expectUri);
// when
ResultActions resultActions = mockMvc.perform(
MockMvcRequestBuilders.get("/api/v1/oauth/{platform}/login", platform.name().toLowerCase())
);
// then
resultActions.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl(expectUri.toString()));
}
}
컨트롤러에 요청을 보내면 OAuth2 제공자에게 Authorization_Code를 요청할 수 있는 새로운 URI로 리다이렉트 됐다는 것을 검증하는 코드이다.
하지만, 다음과 같은 에러를 마주쳤다.
문제 발생
***************************
APPLICATION FAILED TO START
***************************
Description:
A component required a bean named 'entityManagerFactory' that could not be found.
Action:
Consider defining a bean named 'entityManagerFactory' in your configuration.
JPA와 관련된 의존성을 위 테스트 코드에서는 전혀 사용하고 있지 않은데
“님아 entityManagerFactory 빈 못 찾겠음. 님 설정에서 entityManagerFactory ←이 이름으로 빨리 주입 하셈” 라고 말해주고 있었다.
EntityManagerFactory를 주입해주면 끝나는 에러지만, 컨트롤러 단위 테스트에서 쓰고 있지 않은 JPA 관련 빈을 굳이 추가해 줄 이유는 없었다.
1차 시도
나는 OAuthController에서 의존성을 주입받고 있는 OAuthLoginService가 또 의존하고 있는 UserService, OAuthClient를 @MockBean으로 주입하고 있지 않아서 발생되는 문제인가? 라고 생각하여 이를 @MockBean으로 주입해보았다.
@WebMvcTest(OAuthController.class)
class OAuthControllerTest {
@Autowired
protected ObjectMapper objectMapper;
@Autowired
protected MockMvc mockMvc;
@MockBean
protected OAuthRedirectUriProviderComposite redirectUriProvider;
// +++ 추가
@MockBean
protected OAuthLoginService oAuthLoginService;
// +++ 추가
@MockBean
protected OAuthClientComposite oAuthClient;
// +++ 추가
@MockBean
protected UserService userService;
@BeforeEach
void setup(WebApplicationContext context) {
MockMvcBuilders.webAppContextSetup(context)
.alwaysDo(print())
.build();
}
@Test
void 플랫폼에_맞는_AuthorizationCode를_받기위한_요청_URI를_반환한다() throws Exception {
// given
// 나중에 Fixture 변경
Platform platform = Platform.KAKAO;
URI expectUri = UriComponentsBuilder.fromUri(URI.create("www.provider.com" + "/oauth/authorize"))
.queryParam("client_id", "test_id")
.queryParam("client_secret", "test_secret")
.queryParam("redirect_uri", "test_front_redirect_uri")
.queryParam("response_type", "code")
.build().toUri();
given(redirectUriProvider.generateRedirectUri(platform)).willReturn(expectUri);
// when
ResultActions resultActions = mockMvc.perform(
MockMvcRequestBuilders.get("/api/v1/oauth/{platform}/login", platform.name().toLowerCase())
);
// then
resultActions.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl(expectUri.toString()));
}
}
그런데 여전히 같은 에러를 내뿜었다.
그러다가, 스택 오버 플로우에서 다음과 같은 글을 보고, 문제를 이해할 수 있었다.
왜 발생하였는가?
https://stackoverflow.com/questions/56477889/spring-boot-webmvctest-require-entitymanager
위 링크의 글을 요약하자면, @SpringBootApplication이 붙어있는 클래스에서 시작을 할 때 InfraConfiguration.class라는 파일이 EntityManager를 필요로 하기 때문에 발생하는 문제이고, 이를 해결하기 위해서는 WebConfig 클래스로 따로 생성해서 Import를 @SpringBootApplication에서 분리하라고 하였다.
2차 시도
나는 멀티 모듈을 사용하여 개발을 하고 있기 때문에 api, core, infrastructure, support 각 4개의 모듈이 따로 분리되어 있고, api 모듈에서 @SpringBootApplication을 실행할 때 각 모듈의 원활한 빈 주입을 위해 타 모듈의 BasePackage를 @ComponentScan을 통해 처리한다.
SupportConfig.java
@ComponentScan(basePackages = "com.hangongsu.support", lazyInit = true)
public class SupportConfig {
}
CoreConfig.java
@ComponentScan(basePackages = "com.hangongsu.core", lazyInit = true)
public class CoreConfig {
}
InfraStructureConfig.java
@ComponentScan(basePackages = "com.hangongsu.infrastructure", lazyInit = true)
public class InfraStructureConfig {
}
이 중, Core와 InfraStructure에서 JPA를 필요로 하는데, 내 현재 메인 애플리케이션 클래스에서는 이 두 모듈을 Import하고 있었다.
Application.java
@Import({SupportConfig.class, CoreConfig.class, InfraStructureConfig.class})
@SpringBootApplication
public class HangongsuApplication {
public static void main(String[] args) {
SpringApplication.run(HangongsuApplication.class, args);
}
}
그래서 이 Import문을 하나의 독립적인 클래스 ModuleImportConfig로 분리하였다.
@Configuration
@Import({SupportConfig.class, CoreConfig.class, InfraStructureConfig.class})
public class ModuleImportConfig {
}
그리고 테스트 코드를 실행해봤더니,,
@WebMvcTest(OAuthController.class)
class OAuthControllerTest {
@Autowired
protected ObjectMapper objectMapper;
@Autowired
protected MockMvc mockMvc;
@MockBean
protected OAuthRedirectUriProviderComposite redirectUriProvider;
@MockBean
protected OAuthLoginService oAuthLoginService;
@BeforeEach
void setup(WebApplicationContext context) {
MockMvcBuilders.webAppContextSetup(context)
.alwaysDo(print())
.build();
}
@Test
void 플랫폼에_맞는_AuthorizationCode를_받기위한_요청_URI를_반환한다() throws Exception {
// given
// 나중에 Fixture 변경
Platform platform = Platform.KAKAO;
URI expectUri = UriComponentsBuilder.fromUri(URI.create("www.provider.com" + "/oauth/authorize"))
.queryParam("client_id", "test_id")
.queryParam("client_secret", "test_secret")
.queryParam("redirect_uri", "test_front_redirect_uri")
.queryParam("response_type", "code")
.build().toUri();
given(redirectUriProvider.generateRedirectUri(platform)).willReturn(expectUri);
// when
ResultActions resultActions = mockMvc.perform(
MockMvcRequestBuilders.get("/api/v1/oauth/{platform}/login", platform.name().toLowerCase())
);
// then
resultActions.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl(expectUri.toString()));
}
}

성공했다!
결론
단순하게 문제 해결을 하기보다는 좀 더 근본적으로 왜 이런 문제가 발생했는지를 차근차근 살펴보는 행동을 지속적으로 노력해야 성장 가능하다
'Kotlin & Java' 카테고리의 다른 글
Mockito 예외 - org.mockito.exceptions.misusing.MissingMethodInvocationException:when() requires an argument which has to be 'a method call on a mock'. (0) | 2024.08.23 |
---|---|
코틀린 코딩 컨벤션 (3) | 2023.12.18 |
코틀린의 자료형과 자바의 자료형 (1) | 2023.12.07 |
코틀린의 Property를 자바 Field로 디컴파일 해보자 (0) | 2023.12.05 |
코틀린의 생성자 (0) | 2023.12.05 |
발단
나는 최근에 소셜 로그인을 구현하고, 그에 따른 테스트 코드를 JUnit5를 사용해 작성하고 있었다.
그 중에서도 @WebMvcTest를 통해 컨트롤러 단위 테스트를 작성하고 있었다.
테스트 코드
테스트 코드 내용은 간단하다.
@WebMvcTest(OAuthController.class)
class OAuthControllerTest {
@Autowired
protected ObjectMapper objectMapper;
@Autowired
protected MockMvc mockMvc;
@MockBean
protected OAuthRedirectUriProviderComposite redirectUriProvider;
@BeforeEach
void setup(WebApplicationContext context) {
MockMvcBuilders.webAppContextSetup(context)
.alwaysDo(print())
.build();
}
@Test
void 플랫폼에_맞는_AuthorizationCode를_받기위한_요청_URI를_반환한다() throws Exception {
// given
// 나중에 Fixture 변경
Platform platform = Platform.KAKAO;
URI expectUri = UriComponentsBuilder.fromUri(URI.create("www.provider.com" + "/oauth/authorize"))
.queryParam("client_id", "test_id")
.queryParam("client_secret", "test_secret")
.queryParam("redirect_uri", "test_front_redirect_uri")
.queryParam("response_type", "code")
.build().toUri();
given(redirectUriProvider.generateRedirectUri(platform)).willReturn(expectUri);
// when
ResultActions resultActions = mockMvc.perform(
MockMvcRequestBuilders.get("/api/v1/oauth/{platform}/login", platform.name().toLowerCase())
);
// then
resultActions.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl(expectUri.toString()));
}
}
컨트롤러에 요청을 보내면 OAuth2 제공자에게 Authorization_Code를 요청할 수 있는 새로운 URI로 리다이렉트 됐다는 것을 검증하는 코드이다.
하지만, 다음과 같은 에러를 마주쳤다.
문제 발생
***************************
APPLICATION FAILED TO START
***************************
Description:
A component required a bean named 'entityManagerFactory' that could not be found.
Action:
Consider defining a bean named 'entityManagerFactory' in your configuration.
JPA와 관련된 의존성을 위 테스트 코드에서는 전혀 사용하고 있지 않은데
“님아 entityManagerFactory 빈 못 찾겠음. 님 설정에서 entityManagerFactory ←이 이름으로 빨리 주입 하셈” 라고 말해주고 있었다.
EntityManagerFactory를 주입해주면 끝나는 에러지만, 컨트롤러 단위 테스트에서 쓰고 있지 않은 JPA 관련 빈을 굳이 추가해 줄 이유는 없었다.
1차 시도
나는 OAuthController에서 의존성을 주입받고 있는 OAuthLoginService가 또 의존하고 있는 UserService, OAuthClient를 @MockBean으로 주입하고 있지 않아서 발생되는 문제인가? 라고 생각하여 이를 @MockBean으로 주입해보았다.
@WebMvcTest(OAuthController.class)
class OAuthControllerTest {
@Autowired
protected ObjectMapper objectMapper;
@Autowired
protected MockMvc mockMvc;
@MockBean
protected OAuthRedirectUriProviderComposite redirectUriProvider;
// +++ 추가
@MockBean
protected OAuthLoginService oAuthLoginService;
// +++ 추가
@MockBean
protected OAuthClientComposite oAuthClient;
// +++ 추가
@MockBean
protected UserService userService;
@BeforeEach
void setup(WebApplicationContext context) {
MockMvcBuilders.webAppContextSetup(context)
.alwaysDo(print())
.build();
}
@Test
void 플랫폼에_맞는_AuthorizationCode를_받기위한_요청_URI를_반환한다() throws Exception {
// given
// 나중에 Fixture 변경
Platform platform = Platform.KAKAO;
URI expectUri = UriComponentsBuilder.fromUri(URI.create("www.provider.com" + "/oauth/authorize"))
.queryParam("client_id", "test_id")
.queryParam("client_secret", "test_secret")
.queryParam("redirect_uri", "test_front_redirect_uri")
.queryParam("response_type", "code")
.build().toUri();
given(redirectUriProvider.generateRedirectUri(platform)).willReturn(expectUri);
// when
ResultActions resultActions = mockMvc.perform(
MockMvcRequestBuilders.get("/api/v1/oauth/{platform}/login", platform.name().toLowerCase())
);
// then
resultActions.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl(expectUri.toString()));
}
}
그런데 여전히 같은 에러를 내뿜었다.
그러다가, 스택 오버 플로우에서 다음과 같은 글을 보고, 문제를 이해할 수 있었다.
왜 발생하였는가?
https://stackoverflow.com/questions/56477889/spring-boot-webmvctest-require-entitymanager
위 링크의 글을 요약하자면, @SpringBootApplication이 붙어있는 클래스에서 시작을 할 때 InfraConfiguration.class라는 파일이 EntityManager를 필요로 하기 때문에 발생하는 문제이고, 이를 해결하기 위해서는 WebConfig 클래스로 따로 생성해서 Import를 @SpringBootApplication에서 분리하라고 하였다.
2차 시도
나는 멀티 모듈을 사용하여 개발을 하고 있기 때문에 api, core, infrastructure, support 각 4개의 모듈이 따로 분리되어 있고, api 모듈에서 @SpringBootApplication을 실행할 때 각 모듈의 원활한 빈 주입을 위해 타 모듈의 BasePackage를 @ComponentScan을 통해 처리한다.
SupportConfig.java
@ComponentScan(basePackages = "com.hangongsu.support", lazyInit = true)
public class SupportConfig {
}
CoreConfig.java
@ComponentScan(basePackages = "com.hangongsu.core", lazyInit = true)
public class CoreConfig {
}
InfraStructureConfig.java
@ComponentScan(basePackages = "com.hangongsu.infrastructure", lazyInit = true)
public class InfraStructureConfig {
}
이 중, Core와 InfraStructure에서 JPA를 필요로 하는데, 내 현재 메인 애플리케이션 클래스에서는 이 두 모듈을 Import하고 있었다.
Application.java
@Import({SupportConfig.class, CoreConfig.class, InfraStructureConfig.class})
@SpringBootApplication
public class HangongsuApplication {
public static void main(String[] args) {
SpringApplication.run(HangongsuApplication.class, args);
}
}
그래서 이 Import문을 하나의 독립적인 클래스 ModuleImportConfig로 분리하였다.
@Configuration
@Import({SupportConfig.class, CoreConfig.class, InfraStructureConfig.class})
public class ModuleImportConfig {
}
그리고 테스트 코드를 실행해봤더니,,
@WebMvcTest(OAuthController.class)
class OAuthControllerTest {
@Autowired
protected ObjectMapper objectMapper;
@Autowired
protected MockMvc mockMvc;
@MockBean
protected OAuthRedirectUriProviderComposite redirectUriProvider;
@MockBean
protected OAuthLoginService oAuthLoginService;
@BeforeEach
void setup(WebApplicationContext context) {
MockMvcBuilders.webAppContextSetup(context)
.alwaysDo(print())
.build();
}
@Test
void 플랫폼에_맞는_AuthorizationCode를_받기위한_요청_URI를_반환한다() throws Exception {
// given
// 나중에 Fixture 변경
Platform platform = Platform.KAKAO;
URI expectUri = UriComponentsBuilder.fromUri(URI.create("www.provider.com" + "/oauth/authorize"))
.queryParam("client_id", "test_id")
.queryParam("client_secret", "test_secret")
.queryParam("redirect_uri", "test_front_redirect_uri")
.queryParam("response_type", "code")
.build().toUri();
given(redirectUriProvider.generateRedirectUri(platform)).willReturn(expectUri);
// when
ResultActions resultActions = mockMvc.perform(
MockMvcRequestBuilders.get("/api/v1/oauth/{platform}/login", platform.name().toLowerCase())
);
// then
resultActions.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl(expectUri.toString()));
}
}

성공했다!
결론
단순하게 문제 해결을 하기보다는 좀 더 근본적으로 왜 이런 문제가 발생했는지를 차근차근 살펴보는 행동을 지속적으로 노력해야 성장 가능하다
'Kotlin & Java' 카테고리의 다른 글
Mockito 예외 - org.mockito.exceptions.misusing.MissingMethodInvocationException:when() requires an argument which has to be 'a method call on a mock'. (0) | 2024.08.23 |
---|---|
코틀린 코딩 컨벤션 (3) | 2023.12.18 |
코틀린의 자료형과 자바의 자료형 (1) | 2023.12.07 |
코틀린의 Property를 자바 Field로 디컴파일 해보자 (0) | 2023.12.05 |
코틀린의 생성자 (0) | 2023.12.05 |