CLOVER🍀

That was when it all began.

Spring TestずMockitoMockBeanを合わせお䜿った時の挙動を確認する

これは、なにをしたくお曞いたもの

Spring Bootのテスト機胜を䜿うず、Mockitoず簡単に組み合わせられるようになりたす。

こちらを䜿った時の動䜜を、ちゃんず芋おおきたいなず思いたしお。

Spring BootずMockito

Spring BootのMockitoに関するドキュメントは、こちら。

Core Features / Testing / Testing Spring Boot Applications / Mocking and Spying Beans

関連するクラスのJavadocはこちら。

org.springframework.boot.test.mock.mockito (Spring Boot 3.1.5 API)

どういうものかずいうず、@MockBeanたたは@SpyBeanを䜿うこずでApplicationContext内のBeanをMockitoのモックに
眮き換えるこずができたす。

Spring Boot includes a @MockBean annotation that can be used to define a Mockito mock for a bean inside your ApplicationContext. You can use the annotation to add new beans or replace a single existing bean definition.

これらのアノテヌションは、以䞋の箇所で䜿甚できたす。

  • テストクラスや、テストクラス内のフィヌルド
  • @Configurationクラスやそのフィヌルド

䜜成したモックは、察象のBeanを䜿甚しおいるクラスにむンゞェクションされたす。぀たり、䟝存するBeanがモックに眮き換えられたす。

When used on a field, the instance of the created mock is also injected.

そしお、モックはテストメ゜ッドの終了埌に自動的にリセットされたす。

Mock beans are automatically reset after each test method.

䟿利ですが泚意点もあり、ドキュメントでは@MockBeanや@SpyBeanの利甚はキャッシュキヌに圱響を䞎え、ApplicationContextの
再利甚ができなくなる可胜性を瀺唆しおいたす。

While Spring’s test framework caches application contexts between tests and reuses a context for tests sharing the same configuration, the use of @MockBean or @SpyBean influences the cache key, which will most likely increase the number of contexts.

これは、Springのコンテナの䜜成が必芁になり、テストの実行時間が䌞びるこずになりたす。

今回は、このあたりの挙動を芋おみようず思いたす。

環境

今回の環境は、こちら。

$ java --version
openjdk 17.0.8.1 2023-08-24
OpenJDK Runtime Environment (build 17.0.8.1+1-Ubuntu-0ubuntu122.04)
OpenJDK 64-Bit Server VM (build 17.0.8.1+1-Ubuntu-0ubuntu122.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.4 (dfbb324ad4a7c8fb0bf182e6d91b0ae20e3d2dd9)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.8.1, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.15.0-87-generic", arch: "amd64", family: "unix"

お題

6぀のSpringのBeanを䜜成し、以䞋のような同じBeanを䜿うような定矩を含む構成にしたす。

classDiagram
    FooMessageService --> MessageService: テスト時にモックにしお䜿う
    BarMessageService --> MessageService: テスト時もそのたた䜿う
    FooSearchService --> SearchService: テスト時にモックにしお䜿う
    BarSearchService --> SearchService: テスト時もそのたた䜿う
    class FooMessageService {
      +getMessage()
    }
    class BarMessageService {
      +getMessage()
    }
    class MessageService {
      +get()
    }
    class FooSearchService {
      +find()
    }
    class BarSearchService {
      +find()
    }
    class SearchService {
      +find()
    }

この時、泚釈のようにテスト時に䟝存先のBeanをモックにするしないを切り替えお挙動を確認したいず思いたす。
テストは、共有するBeanを「䜿う偎」のみ䜜成したす。

Spring Bootプロゞェクトを䜜成する

それでは、たずはSpring Bootプロゞェクトを䜜成したす。今回は䟝存関係は䞍芁なので、以䞋の指定で䜜成。

$ curl -s https://start.spring.io/starter.tgz \
  -d bootVersion=3.1.5 \
  -d javaVersion=17 \
  -d type=maven-project \
  -d name=mock-bean-example \
  -d groupId=org.littlewings \
  -d artifactId=mock-bean-example \
  -d version=0.0.1-SNAPSHOT \
  -d packageName=org.littlewings.spring.test \
  -d baseDir=mock-bean-example | tar zxvf -

展開されたディレクトリ内ぞ移動。

$ cd mock-bean-example

Maven䟝存関係等は、このようになっおいたす。

        <properties>
                <java.version>17</java.version>
        </properties>
        <dependencies>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter</artifactId>
                </dependency>

                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-test</artifactId>
                        <scope>test</scope>
                </dependency>
        </dependencies>

        <build>
                <plugins>
                        <plugin>
                                <groupId>org.springframework.boot</groupId>
                                <artifactId>spring-boot-maven-plugin</artifactId>
                        </plugin>
                </plugins>
        </build>

生成されたクラスは削陀。

$ rm src/main/java/org/littlewings/spring/test/MockBeanExampleApplication.java src/test/java/org/littlewings/spring/test/MockBeanExampleApplicationTests.java

゜ヌスコヌドを䜜成する

゜ヌスコヌドを䜜成しおいきたす。

共有するBean、その1。

src/main/java/org/littlewings/spring/test/service/MessageService.java

package org.littlewings.spring.test.service;

import org.springframework.stereotype.Service;

@Service
public class MessageService {
    public String get() {
        return "Hello World!!";
    }
}

䜿う偎のBean。

src/main/java/org/littlewings/spring/test/service/FooMessageService.java

package org.littlewings.spring.test.service;

import org.springframework.stereotype.Service;

@Service
public class FooMessageService {
    private MessageService messageService;

    public FooMessageService(MessageService messageService) {
        this.messageService = messageService;
    }

    public String getMessage() {
        return messageService.get();
    }
}

src/main/java/org/littlewings/spring/test/service/BarMessageService.java

package org.littlewings.spring.test.service;

import org.springframework.stereotype.Service;

@Service
public class BarMessageService {
    private MessageService messageService;

    public BarMessageService(MessageService messageService) {
        this.messageService = messageService;
    }

    public String getMessage() {
        return messageService.get();
    }
}

共有するBean、その2。

src/main/java/org/littlewings/spring/test/service/SearchService.java

package org.littlewings.spring.test.service;

import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class SearchService {
    public List<String> find() {
        return List.of("Java", "Spring");
    }
}

䜿う偎のBean。

src/main/java/org/littlewings/spring/test/service/FooSearchService.java

package org.littlewings.spring.test.service;

import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class FooSearchService {
    private SearchService searchService;

    public FooSearchService(SearchService searchService) {
        this.searchService = searchService;
    }

    public List<String> find() {
        return searchService.find();
    }
}

src/main/java/org/littlewings/spring/test/service/BarSearchService.java

package org.littlewings.spring.test.service;

import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BarSearchService {
    private SearchService searchService;

    public BarSearchService(SearchService searchService) {
        this.searchService = searchService;
    }

    public List<String> find() {
        return searchService.find();
    }
}

mainメ゜ッドを持ったクラスも甚意しおおきたす。

src/main/java/org/littlewings/spring/test/App.java

package org.littlewings.spring.test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {
    public static void main(String... args) {
        SpringApplication.run(App.class, args);
    }
}

今回は、テストの構成に䜿われるだけですが。

テストを曞いお確認する

それでは、テストを曞いお確認しおいきたす。

今回はテストの実行順クラスを含むを固定したいので、以䞋の蚭定を入れおおきたす。

src/test/resources/junit-platform.properties

junit.jupiter.testclass.order.default=org.junit.jupiter.api.ClassOrderer$OrderAnnotation

確認は、mvn testで実斜しおいきたす。

モックがテストごずにリセットされるこずを確認する

たずはこんなテストを甚意。

src/test/java/org/littlewings/spring/test/service/FooMessageServiceTest.java

package org.littlewings.spring.test.service;

import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.MockReset;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

@SpringBootTest
@TestClassOrder(ClassOrderer.OrderAnnotation.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Order(1)
class FooMessageServiceTest {
    @Autowired
    private FooMessageService fooMessageService;

    @MockBean
    private MessageService messageService;

    @Test
    @Order(1)
    void test1() {
        assertThat(mockingDetails(messageService).isMock()).isTrue();

        when(messageService.get()).thenReturn("Hello Mock!!");

        assertThat(fooMessageService.getMessage()).isEqualTo("Hello Mock!!");

        verify(messageService, times(1)).get();
    }

    @Test
    @Order(2)
    void test2() {
        assertThat(mockingDetails(messageService).isMock()).isTrue();

        assertThat(fooMessageService.getMessage()).isNull();
    }
}

䟝存するBeanをモックにしたす。

    @MockBean
    private MessageService messageService;

テストは、ずもにモックであるこずを確認しおから、挙動を芋おいきたす。モックのセットアップは片方だけ行っおおき、テストの
実行順はモックのセットアップを行う方が先になるようにしおいたす。

    @Test
    @Order(1)
    void test1() {
        assertThat(mockingDetails(messageService).isMock()).isTrue();

        when(messageService.get()).thenReturn("Hello Mock!!");

        assertThat(fooMessageService.getMessage()).isEqualTo("Hello Mock!!");

        verify(messageService, times(1)).get();
    }

    @Test
    @Order(2)
    void test2() {
        assertThat(mockingDetails(messageService).isMock()).isTrue();

        assertThat(fooMessageService.getMessage()).isNull();
    }

テストを実行。

$ mvn test -Dtest=FooMessageServiceTest

このテストはパスしたす。

ではここで、@MockBeanの蚭定を以䞋のように倉曎したす。

    @MockBean(reset = MockReset.NONE)
    private MessageService messageService;

するず、テストが倱敗するようになりたす。

[ERROR] Failures:
[ERROR]   FooMessageServiceTest.test2:40
expected: null
 but was: "Hello Mock!!"

これは、ドキュメントにあった以䞋の挙動を打ち消したものです。぀たり、モックがリセットされなくなり別のテストに圱響を䞎えるように
なりたした。

Mock beans are automatically reset after each test method.

぀たり、なにもモックのセットアップの状態をしおいない以䞋のテストが、先に実行されおモックのセットアップを行った別のテストの
内容を芋おいるこずになりたす。

    @Test
    @Order(2)
    void test2() {
        assertThat(mockingDetails(messageService).isMock()).isTrue();

        assertThat(fooMessageService.getMessage()).isNull();
    }

デフォルトだず、以䞋の指定ず同じずいうこずになりたすね。

    @MockBean(reset = MockReset.AFTER)
    private MessageService messageService;
モックを䜿わないテストを远加しお、Springのコンテキストの構築の様子を芋おみる

次は、モックを䜿わないテストを曞いおみたす。

src/test/java/org/littlewings/spring/test/service/BarMessageServiceTest.java

package org.littlewings.spring.test.service;

import org.junit.jupiter.api.ClassOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestClassOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.util.ReflectionTestUtils;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mockingDetails;

@SpringBootTest
@TestClassOrder(ClassOrderer.OrderAnnotation.class)
@Order(2)
class BarMessageServiceTest {
    @Autowired
    private BarMessageService barMessageService;

    @Test
    void test() {
        Object messageService = ReflectionTestUtils.getField(barMessageService, "messageService");
        assertThat(mockingDetails(messageService).isMock()).isFalse();

        assertThat(barMessageService.getMessage()).isEqualTo("Hello World!!");
    }
}

䜿甚しおいるBeanが䟝存しおいるのは、先ほど別のテストでモックにしたBeanですが、こちらがモックになっおいないこずを確認したす。

    @Test
    void test() {
        Object messageService = ReflectionTestUtils.getField(barMessageService, "messageService");
        assertThat(mockingDetails(messageService).isMock()).isFalse();

        assertThat(barMessageService.getMessage()).isEqualTo("Hello World!!");
    }

たずは単䜓で実行。

$ mvn test -Dtest=BarMessageServiceTest

こちらは問題なくパスしたす。

次に、䞡方ずも実行。

$ mvn test -Dtest=*MessageServiceTest

こちらも問題なくパスしたすが、以䞋のようにSpringのコンテキストの構築がそれぞれで行われおしたいたす。

16:39:08.224 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [org.littlewings.spring.test.service.FooMessageServiceTest]: FooMessageServiceTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
16:39:08.307 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration org.littlewings.spring.test.App for test class org.littlewings.spring.test.service.FooMessageServiceTest

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.5)

2023-10-22T16:39:08.667+09:00  INFO 18914 --- [           main] o.l.s.t.service.FooMessageServiceTest    : Starting FooMessageServiceTest using Java 17.0.8.1 with PID 18914 (started by xxxxx in /path/to/mock-bean-example)


〜省略〜


2023-10-22T16:39:10.338+09:00  INFO 18914 --- [           main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [org.littlewings.spring.test.service.BarMessageServiceTest]: BarMessageServiceTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2023-10-22T16:39:10.343+09:00  INFO 18914 --- [           main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration org.littlewings.spring.test.App for test class org.littlewings.spring.test.service.BarMessageServiceTest

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.5)

2023-10-22T16:39:10.405+09:00  INFO 18914 --- [           main] o.l.s.t.service.BarMessageServiceTest    : Starting BarMessageServiceTest using Java 17.0.8.1 with PID 18914 (started by xxxxx in /path/to/mock-bean-example)

〜省略〜

これは、ドキュメントに曞かれおいたように、コンテキストの構成情報が異なるため再利甚ができなくなったこずを指しおいたす。

While Spring’s test framework caches application contexts between tests and reuses a context for tests sharing the same configuration, the use of @MockBean or @SpyBean influences the cache key, which will most likely increase the number of contexts.

他のグルヌピングのテストも远加しお、コンテキストの構築の様子を芋おみる

同じBeanを共有する構成ずしたコヌドは、もう1組ありたした。こちらのテストコヌドも远加しおみたす。

src/test/java/org/littlewings/spring/test/service/FooSearchServiceTest.java

package org.littlewings.spring.test.service;

import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

@SpringBootTest
@Order(3)
class FooSearchServiceTest {
    @Autowired
    private FooSearchService fooSearchService;

    @MockBean
    private SearchService searchService;

    @Order(1)
    void test1() {
        assertThat(mockingDetails(searchService).isMock()).isTrue();

        when(searchService.find()).thenReturn(List.of("Mockito"));

        assertThat(fooSearchService.find()).isEqualTo(List.of("Mockito"));

        verify(searchService, times(1)).find();
    }

    @Test
    @Order(2)
    void test2() {
        assertThat(mockingDetails(searchService).isMock()).isTrue();

        assertThat(searchService.find()).isEmpty();
    }
}

src/test/java/org/littlewings/spring/test/service/BarSearchServiceTest.java

package org.littlewings.spring.test.service;

import org.junit.jupiter.api.ClassOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestClassOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mockingDetails;

@SpringBootTest
@TestClassOrder(ClassOrderer.OrderAnnotation.class)
@Order(4)
class BarSearchServiceTest {
    @Autowired
    private BarSearchService barSearchService;

    @Test
    void test1() {
        Object searchService = ReflectionTestUtils.getField(barSearchService, "searchService");
        assertThat(mockingDetails(searchService).isMock()).isFalse();

        assertThat(barSearchService.find()).isEqualTo(List.of("Java", "Spring"));
    }
}

テストずしお実装しおいる内容こそ異なりたすが、確認しおいるポむントは先ほどのものず同じです。

ずいうわけで、テストを実行しおみたす。

$ mvn test

テストは問題なくパスしたすが、Springのコンテキストの構築は3回行われたした。

16:45:35.204 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [org.littlewings.spring.test.service.FooMessageServiceTest]: FooMessageServiceTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
16:45:35.290 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration org.littlewings.spring.test.App for test class org.littlewings.spring.test.service.FooMessageServiceTest

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.5)

2023-10-22T16:45:35.623+09:00  INFO 19254 --- [           main] o.l.s.t.service.FooMessageServiceTest    : Starting FooMessageServiceTest using Java 17.0.8.1 with PID 19254 (started by xxxxx in /path/to/mock-bean-example)


〜省略〜


2023-10-22T16:45:37.138+09:00  INFO 19254 --- [           main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [org.littlewings.spring.test.service.BarMessageServiceTest]: BarMessageServiceTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2023-10-22T16:45:37.142+09:00  INFO 19254 --- [           main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration org.littlewings.spring.test.App for test class org.littlewings.spring.test.service.BarMessageServiceTest

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.5)

2023-10-22T16:45:37.203+09:00  INFO 19254 --- [           main] o.l.s.t.service.BarMessageServiceTest    : Starting BarMessageServiceTest using Java 17.0.8.1 with PID 19254 (started by xxxxx in /path/to/mock-bean-example)


〜省略〜


2023-10-22T16:45:37.345+09:00  INFO 19254 --- [           main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [org.littlewings.spring.test.service.FooSearchServiceTest]: FooSearchServiceTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2023-10-22T16:45:37.349+09:00  INFO 19254 --- [           main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration org.littlewings.spring.test.App for test class org.littlewings.spring.test.service.FooSearchServiceTest

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.5)

2023-10-22T16:45:37.388+09:00  INFO 19254 --- [           main] o.l.s.test.service.FooSearchServiceTest  : Starting FooSearchServiceTest using Java 17.0.8.1 with PID 19254 (started by xxxxx in /path/to/mock-bean-example)


〜省略〜


2023-10-22T16:45:37.556+09:00  INFO 19254 --- [           main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [org.littlewings.spring.test.service.BarSearchServiceTest]: BarSearchServiceTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2023-10-22T16:45:37.558+09:00  INFO 19254 --- [           main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration org.littlewings.spring.test.App for test class org.littlewings.spring.test.service.BarSearchServiceTest

4回ではないのは、モックを䜿っおいないBarMessageServiceTestずBarSearchServiceTestではキャッシュしたコンテキストが䜿い回せる
からですね。

ログをよく芋るず、こういう内容はい぀も出おいたすが

16:45:35.204 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [org.littlewings.spring.test.service.FooMessageServiceTest]: FooMessageServiceTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
16:45:35.290 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration org.littlewings.spring.test.App for test class org.littlewings.spring.test.service.FooMessageServiceTest

コンテキストを䜜り盎すこずになった時は、以䞋のログが出力されるんですね。

2023-10-22T16:45:37.203+09:00  INFO 19254 --- [           main] o.l.s.t.service.BarMessageServiceTest    : Starting BarMessageServiceTest using Java 17.0.8.1 with PID 19254 (started by xxxxx in /path/to/mock-bean-example)

埌続で起動したBarSearchServiceTestには、このログがありたせんでした。

では、モックを䜿ったテストのみではどうでしょうか。

$ mvn test -Dtest=Foo*ServiceTest

こちらもキヌが異なるので、2回コンテキストが構築されたす。

16:49:29.975 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [org.littlewings.spring.test.service.FooMessageServiceTest]: FooMessageServiceTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
16:49:30.064 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration org.littlewings.spring.test.App for test class org.littlewings.spring.test.service.FooMessageServiceTest

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.5)

2023-10-22T16:49:30.369+09:00  INFO 19387 --- [           main] o.l.s.t.service.FooMessageServiceTest    : Starting FooMessageServiceTest using Java 17.0.8.1 with PID 19387 (started by xxxxx in /path/to/mock-bean-example)


〜省略〜


2023-10-22T16:49:31.727+09:00  INFO 19387 --- [           main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [org.littlewings.spring.test.service.FooSearchServiceTest]: FooSearchServiceTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2023-10-22T16:49:31.731+09:00  INFO 19387 --- [           main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration org.littlewings.spring.test.App for test class org.littlewings.spring.test.service.FooSearchServiceTest

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.5)

2023-10-22T16:49:31.783+09:00  INFO 19387 --- [           main] o.l.s.test.service.FooSearchServiceTest  : Starting FooSearchServiceTest using Java 17.0.8.1 with PID 19387 (started by xxxxx in /path/to/mock-bean-example)
テスト察象は同じで、モックを䜿わないテストを远加する

ここたでで、なんずなく挙動は確認できた気がしたす。

それでは「テスト察象は同じで、モックは䜿わないパタヌン」のテストを远加しおみたしょう。結果はわかる気はしたすが。

こういうものですね。

src/test/java/org/littlewings/spring/test/service/FooMessageService2Test.java

package org.littlewings.spring.test.service;

import org.junit.jupiter.api.ClassOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestClassOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.util.ReflectionTestUtils;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mockingDetails;

@SpringBootTest
@TestClassOrder(ClassOrderer.OrderAnnotation.class)
@Order(5)
public class FooMessageService2Test {
    @Autowired
    private FooMessageService fooMessageService;

    @Test
    void test1() {
        Object messageService = ReflectionTestUtils.getField(fooMessageService, "messageService");
        assertThat(mockingDetails(messageService).isMock()).isFalse();

        assertThat(fooMessageService.getMessage()).isEqualTo("Hello World!!");
    }
}

実行。

$ mvn test -Dtest=FooMessageService*Test

予想できる挙動ですが、コンテキストが2回構築されたす。

16:55:55.763 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [org.littlewings.spring.test.service.FooMessageServiceTest]: FooMessageServiceTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
16:55:55.862 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration org.littlewings.spring.test.App for test class org.littlewings.spring.test.service.FooMessageServiceTest

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.5)

2023-10-22T16:55:56.185+09:00  INFO 19782 --- [           main] o.l.s.t.service.FooMessageServiceTest    : Starting FooMessageServiceTest using Java 17.0.8.1 with PID 19782 (started by xxxxx in /path/to/mock-bean-example)


〜省略〜


2023-10-22T16:55:57.792+09:00  INFO 19782 --- [           main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [org.littlewings.spring.test.service.FooMessageService2Test]: FooMessageService2Test does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2023-10-22T16:55:57.798+09:00  INFO 19782 --- [           main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration org.littlewings.spring.test.App for test class org.littlewings.spring.test.service.FooMessageService2Test

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.5)

2023-10-22T16:55:57.843+09:00  INFO 19782 --- [           main] o.l.s.t.service.FooMessageService2Test   : Starting FooMessageService2Test using Java 17.0.8.1 with PID 19782 (started by xxxxx in /path/to/mock-bean-example)

党郚のテストを実行した堎合は、モックを䜿わないテストはそれぞれでコンテキストが共有できるので、コンテキスの構築は3回になりたす。

$ mvn test
モック察象も同じテストを远加する

最埌にこの状態で、モック察象も同じテストをもうひず぀远加したす。FooMessageServiceTestのコピヌですね。

src/test/java/org/littlewings/spring/test/service/FooMessageService3Test.java

package org.littlewings.spring.test.service;

import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

@SpringBootTest
@TestClassOrder(ClassOrderer.OrderAnnotation.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Order(6)
class FooMessageService3Test {
    @Autowired
    private FooMessageService fooMessageService;

    @MockBean
    private MessageService messageService;

    @Test
    @Order(1)
    void test1() {
        assertThat(mockingDetails(messageService).isMock()).isTrue();

        when(messageService.get()).thenReturn("Hello Mock!!");

        assertThat(fooMessageService.getMessage()).isEqualTo("Hello Mock!!");

        verify(messageService, times(1)).get();
    }

    @Test
    @Order(2)
    void test2() {
        assertThat(mockingDetails(messageService).isMock()).isTrue();

        assertThat(fooMessageService.getMessage()).isNull();
    }
}

これはどうなるかずいうず

$ mvn test -Dtest=FooMessageService*Test

先ほどモックを䜿わないテストを远加したので、コンテキストの構築回数は2回になっおいたすが、モックも含めお定矩が同じであれば
コンテキストが䜿い回されるFooMessageServiceTestずFooMessageService3Testで定矩が同じなので、同じコンテキストが再利甚される
こずが確認できたす。

ing.test.service.FooMessageServiceTest]: FooMessageServiceTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
17:08:26.641 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration org.littlewings.spring.test.App for test class org.littlewings.spring.test.service.FooMessageServiceTest

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.5)

2023-10-22T17:08:27.049+09:00  INFO 20970 --- [           main] o.l.s.t.service.FooMessageServiceTest    : Starting FooMessageServiceTest using Java 17.0.8.1 with PID 20970 (started by xxxxx in /path/to/mock-bean-example)


〜省略〜


2023-10-22T17:08:29.538+09:00  INFO 20970 --- [           main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [org.littlewings.spring.test.service.FooMessageService2Test]: FooMessageService2Test does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2023-10-22T17:08:29.546+09:00  INFO 20970 --- [           main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration org.littlewings.spring.test.App for test class org.littlewings.spring.test.service.FooMessageService2Test

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.5)

2023-10-22T17:08:29.644+09:00  INFO 20970 --- [           main] o.l.s.t.service.FooMessageService2Test   : Starting FooMessageService2Test using Java 17.0.8.1 with PID 20970 (started by xxxxx in /path/to/mock-bean-example)


〜省略〜


2023-10-22T17:08:29.948+09:00  INFO 20970 --- [           main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [org.littlewings.spring.test.service.FooMessageService3Test]: FooMessageService3Test does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2023-10-22T17:08:29.956+09:00  INFO 20970 --- [           main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration org.littlewings.spring.test.App for test class org.littlewings.spring.test.service.FooMessageService3Test

こんなずころでしょうか。

おわりに

Spring TestずMockito@MockBeanを組み合わせた時に、どのような動䜜になるのか確認しおみたした。
※@SpyBeanはパスしおいたすが

@MockBeanはSpring BootでMockitoを䜿う時には䟿利ですが、こういう挙動になるずいうのも抌さえおおかないずいけない気がしたすね。

テストの速床が問題になっおくるような時は、@MockBeanに頌らずに自分でモックずしお䜜成したBeanを差し替えお、テストの終了時に
戻すようなこずをするのが良さそうな気がしたす。