Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

my-test-util - now()가 실행되는 위치가 스프링 설정 클레스에 있는 경우? #2

Open
1 task done
fp024 opened this issue Mar 7, 2024 · 2 comments
Assignees

Comments

@fp024
Copy link
Owner

fp024 commented Mar 7, 2024

문제

그동안은 잘 느끼지 못하던 문제인데...

Spring Security in Action 1판의 19장 예제를 확인하다가.. 아래 문제에 대해 알게되었다.

@Configuration
public class ProjectConfig {
    // ...
    private Mono<AuthorizationDecision> getAuthorizationDecisionMono(
            Mono<Authentication> a,
            AuthorizationContext c) {

        String path = getRequestPath(c);

        boolean restrictedTime =
                LocalTime.now().isAfter(LocalTime.NOON);

        if (path.equals("/hello")) {
            return  a.map(isAdmin())
                     .map(auth -> auth && !restrictedTime)
                     .map(AuthorizationDecision::new);
        }

        return Mono.just(new AuthorizationDecision(false));
    }
    // ...
}

권한 부여를 결정하는 조건 메서드가 위처럼 설정 클래스 안쪽에 있는 경우, 문제가 발생하는데..

저자님은 위의 조건을 테스트하는 코드를 따로 만들지는 않으셨는데... 나는 만들어보다가 이 문제를 알게되었다.

  @Test
  @DisplayName(
      "ADMIN 역할을 가지고 있는 유저가 /hello 엔드포인트를 호출 하더라도, " //
          + " 현재시간이 12:00 이후라면 (12:00 < 현재시간) "
          + "애플리케이션은 HTTP 403 FORBIDDEN을 반환해야한다.")
  @WithMockUser(
      username = "john",
      roles = {"ADMIN"})
  void testCallHelloWithMockUser_Afternoon() {
     LocalTime now = LocalTime.of(12, 1);
    FixedDateTestHelper.changeNowLocalTime(
        now, () -> client
          .get() //
          .uri("/hello")
          .exchange()
          .expectStatus()
          .isForbidden()
    );
  }

위 테스트를 실행하면, 테스트를 실행하는 시간에 아래와 같으면 HTTP 200응답이 되어 실패한다.

  • 00:00 <= 테스트 시간 <= 12:00

당연한 문제이긴 했다. 😅

원인

  • 테스트 메서드가 실행되기 전에 이미 스프링의 설정 클래스는 초기화 되기 때문임
  • 메서드 실행 전이 아니고, 설정 클래스가 초기화 되기전에 LocaTime.now()의 반환 시간이 고정되야함.

해결 방향

  • 아마도 @ExtensionWith 이 어노테이션을 @SpringBootTest 보다 앞에 위치시키고 거기에 들어갈 초기화 클래스를 JUnit 5의 BeforeEachCallback, AfterEachCallback 등을 상속하는 형태로 만들어주면 될 것 같은데.... 음... 해봐야 알것 같은데.. 왠지 하면 잘 될 것 같다...😅

해결

다음과 같이 @ExtendWith와 알맞은 시간 타입의 어노테이션을 테스트 메서드에 설정하면 날짜 / 시간 고정이 되도록 했음.

@Slf4j
@ExtendWith(FixedDateExtension.class)
class FixedDateExtensionUsingTests {
  // ---------- ✨ FixedLocalDateTime 테스트 ✨ ----------
  @FixedLocalDateTime(year = 2023, month = 12, dayOfMonth = 25, hour = 12, minute = 5, second = 10)
  @Test
  void testFixedLocalDateTime03() {
    assertThat(LocalDateTime.now()) //
        .isEqualTo(LocalDateTime.of(2023, 12, 25, 12, 5, 10, 0));
  }

  // ---------- ✨ FixedLocalDate 테스트 ✨ ----------
  @FixedLocalDate(year = 2023, month = 12, dayOfMonth = 25)
  @Test
  void testFixedLocalDate03() {
    assertThat(LocalDate.now()) //
        .isEqualTo(LocalDate.of(2023, 12, 25));
  }

  // ---------- ✨ FixedLocalTime 테스트 ✨ ----------
  @FixedLocalTime(hour = 12, minute = 1)
  @Test
  void testFixedLocalTime01() {
    assertThat(LocalTime.now()) //
        .isEqualTo(LocalTime.of(12, 1));
  }
}

이 라이브러리 프로젝트의 자체 테스트를 했을 때는, 정상이었지만,

  • 이 문제를 느끼게 된.. 스프링 설정에 now가 미리 들어갔을 때.. 잘 동작하는지 확인이 필요함. 😅
@fp024
Copy link
Owner Author

fp024 commented Mar 8, 2024

예제를 만들어서 확인해보았는데...

이건 설정클래스 문제가 아니였다.

해당 설정 부분이 고정되는 부분이 아니고, 호출 할 때마다 메서드 내용이 실행 되는데...

해당 요청에 대한 스레드가 새로 만들어질 경우 그 스레드의 now()는 mockStatic이 적용되지 않는다.

15:28:35.484 [Test worker] INFO  org.fp024.examples.MainTests - ### 12:01 ###
15:28:36.137 [parallel-2] INFO  org.fp024.examples.config.WebAuthorizationConfig - ### LocalTime.now(): 15:28:36.136750700

로거를 보면 알 수 있는데 설정 클래스 실행 부분에서 parallel-2 라는 스레드가 새로 생겼고, Mock 설정된 시간이 아닌 현재 시간이 나타난다.

다른 스레드에서 모킹된 정적 메서드를 사용할 수 없다라는 이슈가 있는데 현재 Mockito 에 적용되진 않은 것 같다.

  • https://github.com/mockito/mockito/issues/2142

유틸리티 사용은 편해지긴 했는데... 결국 문제 해결은 안되었음.. 😅

TODO:

  • Mockito가 나중에 버전업이 될 때.. 기능을 추가해주면 확인보자! 💡

fp024 added a commit to fp024/examples that referenced this issue Mar 10, 2024
- 모두 단일 스레드(Test worker)로 실행되서, MockStatic이 원하는대로 동작함을 확인했다.

- fp024/my-utils#2
@fp024
Copy link
Owner Author

fp024 commented Mar 10, 2024

Non-리액티브 방식으로 해보면, 문제가 없을 것 같아서...
예제 프로젝트를 수정해봤는데, 문제가 없음을 확인했다.

  • fp024/examples@b042eca

  • fp024/examples@83c6469

    • UserDetailsServie를 안바꿔도 테스트가 되긴 되었는데, 바꾸는게 맞음.. bootRun으로 실행하면 구성되지 않았다고 경고뜨고 기본유저 / 암호도 만들어짐.😅
  • 로그

    09:41:26.025 [main] INFO  org.fp024.examples.MainTests - ### 12:01 ###
    09:41:26.439 [main] INFO  org.fp024.examples.config.WebAuthorizationConfig - ### LocalTime.now(): 12:01
    

    @FixedLocalTime어노테이션에서 설정한 값대로 테스트 메서드 내부, 설정 클래스 내부가 설정한 시간으로 바뀐 것이 보이고, 둘다 쓰레드가 main으로 같다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant