添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
玩命的馒头  ·  Finished Spring Data ...·  1 周前    · 
买醉的闹钟  ·  Process Engine ...·  1 月前    · 
爱看球的小马驹  ·  使用spring data ...·  1 月前    · 
绅士的拖把  ·  Validation and Error ...·  1 月前    · 
谦和的乌冬面  ·  [SpringBoot]java.lang. ...·  2 月前    · 
豁达的生姜  ·  Martin Manutention - ...·  2 月前    · 
谦逊的佛珠  ·  Can't Run 'npm run ...·  3 月前    · 
坚强的柿子  ·  js循环生成表格-掘金·  7 月前    · 

Spring Boot/Spring Frameworkのテストに関するドキュメントをざっくり眺めてみる - CLOVER🍀

参照するドキュメントとSpring Web MVC のテスト

今回、Spring Web MVC のテストを見ていくにあたり、参照するドキュメントは主に次の2つです。

Spring Bootのテストに関するドキュメント。

Core Features / Testing

さらに絞り込むと、Spring Boot Applicationのテストに関するセクションですね。

Core Features / Testing / Testing Spring Boot Applications

Spring Framework のテストに関するドキュメント。

Testing

こちらも絞り込むと、インテグレーションテストに関するセクションになります。

Testing / Integration Testing

基本的には、Spring Bootのドキュメントを見ていき、テスト自体を書く時に使うクラスに関しては Spring Framework のドキュメントを
必要に応じて見ていく、という感じになります。

Spring Web MVC のテストをする方法ですが、Spring Bootや Spring Framework のテストに関するドキュメントを見ていくと
複数あることに気づきます。

読み進め方ですが、まずはSpring Bootプロジェクトを作成してサンプルプログラムを作り、そこからテストのバリエーションを変えつつ
テストコードを書いていくことにします。

順番は、以下にします。

  • @WebMvcTest を使ったテスト
  • Core Features / Testing / Testing Spring Boot Applications / Auto-configured Spring MVC Tests
  • モック環境でのテスト
  • Core Features / Testing / Testing Spring Boot Applications / Testing with a mock environment
  • 組み込みサーバーを起動してのテスト
  • Core Features / Testing / Testing Spring Boot Applications / Testing with a running server
  • 組み込み Tomcat を起動することになります。

    このあたりを、適宜ドキュメントの内容を絡めながら書いていきたいと思います。 HtmlUnit Selenium を使ったテストは扱いません。

    ちなみに、 Spring Framework のテストのセクションには、 HtmlUnit やWebDriver、 Geb とMockMvcを合わせて使う方法について書かれたり
    しています。興味があれば、こちらをどうぞ。

    Testing / Integration / MockMvc / Testing HtmlUnit Integration

    では、進めていきます。

    今回の環境は、こちら。

    $ java --version
    openjdk 17.0.2 2022-01-18
    OpenJDK Runtime Environment (build 17.0.2+8-Ubuntu-120.04)
    OpenJDK 64-Bit Server VM (build 17.0.2+8-Ubuntu-120.04, mixed mode, sharing)
    $ mvn --version
    Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0)
    Maven home: $HOME/.sdkman/candidates/maven/current
    Java version: 17.0.2, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
    Default locale: ja_JP, platform encoding: UTF-8
    OS name: "linux", version: "5.4.0-107-generic", arch: "amd64", family: "unix"

    まずはSpring Bootプロジェクトを作成します。

    $ curl -s https://start.spring.io/starter.tgz \
      -d bootVersion=2.6.6 \
      -d javaVersion=17 \
      -d name=webmvc-testing \
      -d groupId=org.littlewings \
      -d artifactId=webmvc-testing \
      -d version=0.0.1-SNAPSHOT \
      -d packageName=org.littlewings \
      -d dependencies=web,webflux \
      -d baseDir=webmvc-testing | tar zxvf -

    依存関係に web 以外に webflux が入っていますが、この理由は後で書きます。

    プロジェクト内へ移動。

    $ cd webmvc-testing

    ディレクト リ構成。

    $ tree
    ├── HELP.md
    ├── mvnw
    ├── mvnw.cmd
    ├── pom.xml
    └── src
        ├── main
        │   ├── java
        │   │   └── org
        │   │       └── littlewings
        │   │           └── WebmvcTestingApplication.java
        │   └── resources
        │       ├── application.properties
        │       ├── static
        │       └── templates
        └── test
            └── java
                └── org
                    └── littlewings
                        └── WebmvcTestingApplicationTests.java
    12 directories, 7 files

    Maven での依存関係および プラグイン 設定。

            <dependencies>
                    <dependency>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-starter-web</artifactId>
                    </dependency>
                    <dependency>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-starter-webflux</artifactId>
                    </dependency>
                    <dependency>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-starter-test</artifactId>
                            <scope>test</scope>
                    </dependency>
                    <dependency>
                            <groupId>io.projectreactor</groupId>
                            <artifactId>reactor-test</artifactId>
                            <scope>test</scope>
                    </dependency>
            </dependencies>
            <build>
                    <plugins>
                            <plugin>
                                    <groupId>org.springframework.boot</groupId>
                                    <artifactId>spring-boot-maven-plugin</artifactId>
                            </plugin>
                    </plugins>
            </build>
    

    含まれているソースコードは、こんな感じですね。

    src/main/java/org/littlewings/WebmvcTestingApplication.java

    package org.littlewings;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    @SpringBootApplication
    public class WebmvcTestingApplication {
            public static void main(String[] args) {
                    SpringApplication.run(WebmvcTestingApplication.class, args);
    

    src/test/java/org/littlewings/WebmvcTestingApplicationTests.java

    package org.littlewings;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;
    @SpringBootTest
    class WebmvcTestingApplicationTests {
            @Test
            void contextLoads() {
    

    テストコードには、@SpringBootTestアノテーションが付与されています。

    このあたりは、いったん削除します。

    $ rm src/main/java/org/littlewings/WebmvcTestingApplication.java src/test/java/org/littlewings/WebmvcTestingApplicationTests.java

    @SpringBootTestアノテーションについて

    @SpringBootTestアノテーションについての説明を少し見てみます。

    @SpringBootTestアノテーションは、Spring Bootの機能が必要な場合に@ContextConfigurationアノテーションの代わりに使用します。

    Spring Boot provides a @SpringBootTest annotation, which can be used as an alternative to the standard spring-test @ContextConfiguration annotation when you need Spring Boot features. The annotation works by creating the ApplicationContext used in your tests through SpringApplication. In addition to @SpringBootTest a number of other annotations are also provided for testing more specific slices of an application.

    Core Features / Testing / Testing Spring Boot Applications

    SpringApplicationを使い、ApplicationContextの作成を行います。また、テストでの機能が不足する場合、Auto Configurationを行うための
    機能も提供しています(スライスと読んでいます)。

    Core Features / Testing / Testing Spring Boot Applications / https://docs.spring.io/spring-boot/docs/2.6.6/reference/html/features.html#features.testing.spring-boot-applications.autoconfigured-tests

    そして、Web環境の方に目を向けてみると、@SpringBootTestアノテーションはサーバーを開始しないと書いています。
    ですが、webEnvironment属性を使ってテストの実行方法を決められるようです。

    By default, @SpringBootTest will not start a server. You can use the webEnvironment attribute of @SpringBootTest to further refine how your tests run:

    Core Features / Testing / Testing Spring Boot Applications

    @SpringBootTestアノテーションwebEnvironment属性には、以下の4種類が指定できます。

  • デフォルトの挙動で、Web用のApplicationContextをロードして、モックWeb環境を提供する
  • 組み込みサーバーは起動しない
  • @AutoConfigureMockMvcアノテーションまたは@AutoConfigureWebTestClientアノテーションと組み合わせることで、Webアプリケーションのモックベースのテストが可能になる
  • RANDOM_PORT
  • WebServerApplicationContextをロードして、本物のWeb環境を提供する
  • 組み込みサーバーが起動し、ランダムなポートでリッスンする
  • DEFINED_PORT
  • WebServerApplicationContextをロードして、本物のWeb環境を提供する
  • 組み込みサーバーが起動し、application.propertiesで定義されたポートまたはデフォルトの8080ポートでリッスンする
  • SpringApplicationによってApplicationContextはロードするものの、モックやそれ以外のいずれのWeb環境も提供しない
  • テストをモックWeb環境で行うか、組み込みサーバーで行うか、という感じになります(まったくWeb環境を使わないという選択肢もありますが)。

    Spring Web MVCが使用可能な場合は、Spring Web MVCベースのApplicationContextが構成されるようです。

    If Spring MVC is available, a regular MVC-based application context is configured.

    Core Features / Testing / Testing Spring Boot Applications / Detecting Web Application Type

    サンプルアプリケーションを作成する

    テスト対象のサンプルアプリケーションを作成しましょう。お題は、ちょっとしたechoプログラムにします。

    mainクラス。

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

    package org.littlewings.spring.webmvc;
    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);
    

    RestController。GETとPOST用のメソッドを用意して、POSTの場合はJSONでリクエストを受け取るようにします。

    src/main/java/org/littlewings/spring/webmvc/EchoController.java

    package org.littlewings.spring.webmvc;
    import java.util.Optional;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    @RestController
    @RequestMapping("echo")
    public class EchoController {
        MessageDecorationService messageDecorationService;
        public EchoController(MessageDecorationService messageDecorationService) {
            this.messageDecorationService = messageDecorationService;
        @GetMapping("get")
        public EchoResponse get(@RequestParam String message) {
            Thread.dumpStack();
            return EchoResponse
                    .create(
                            messageDecorationService
                                    .decorate(Optional.ofNullable(message).orElse("Hello World[get]!!")),
                            Thread.currentThread().getName()
        @PostMapping("post")
        public EchoResponse post(@RequestBody EchoRequest echoRequest) {
            Thread.dumpStack();
            return EchoResponse
                    .create(
                            messageDecorationService
                                    .decorate(Optional.ofNullable(echoRequest.getMessage()).orElse("Hello World[post]!!")),
                            Thread.currentThread().getName()
    

    レスポンスには受け取ったメッセージをServiceで加工して返し、この時に動作しているスレッド名を返すようにします。
    それから、RestControllerのメソッド呼び出し時にはスタックトレースを出力するようにしています。

    リクエスト用のクラス。

    src/main/java/org/littlewings/spring/webmvc/EchoRequest.java

    package org.littlewings.spring.webmvc;
    public class EchoRequest {
        String message;
        public static EchoRequest create(String message) {
            EchoRequest echoRequest = new EchoRequest();
            echoRequest.setMessage(message);
            return echoRequest;
        // getter/setterは省略
    

    レスポンス用のクラス。

    src/main/java/org/littlewings/spring/webmvc/EchoResponse.java

    package org.littlewings.spring.webmvc;
    public class EchoResponse {
        String message;
        String threadName;
        public static EchoResponse create(String message, String threadName) {
            EchoResponse echoResponse = new EchoResponse();
            echoResponse.setMessage(message);
            echoResponse.setThreadName(threadName);
            return echoResponse;
        // getter/setterは省略
    

    RestControllerが使うServiceクラス。

    src/main/java/org/littlewings/spring/webmvc/MessageDecorationService.java

    package org.littlewings.spring.webmvc;
    import org.springframework.stereotype.Service;
    @Service
    public class MessageDecorationService {
        public String decorate(String message) {
            return String.format("★★★ %s !!★★★", message);
    

    動作確認。

    $ mvn spring-boot:run

    OKですね。

    $ curl localhost:8080/echo/get?message=Hello
    {"message":"★★★ Hello !!★★★","threadName":"http-nio-8080-exec-1"}
    $ curl -H 'Content-Type: application/json' localhost:8080/echo/post -d '{"message": "Hello"}'
    {"message":"★★★ Hello !!★★★","threadName":"http-nio-8080-exec-2"}

    では、こちらに対してテストを書いていきましょう。なお、依存関係にspring-boot-starter-webfluxが含まれていましたが、特に明示的に
    説明しない限りはspring-boot-starter-webspring-boot-starter-testが依存関係に含まれていればテストは動作します。

    spring-boot-starter-webfluxについては、必要な部分で説明します。

    デフォルトの@SpringBootTestで組み込みサーバーが起動しないことを確認する

    実際のテストに行く前に、@SpringBootTestアノテーションだけでは組み込みサーバーが起動しないことを確認しておきます。

    こんなテストで確認。

    src/test/java/org/littlewings/spring/webmvc/SpringBootTestDefaultTest.java

    package org.littlewings.spring.webmvc;
    import java.io.IOException;
    import java.net.ConnectException;
    import java.net.HttpURLConnection;
    import java.net.URI;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;
    import static org.assertj.core.api.Assertions.assertThatThrownBy;
    @SpringBootTest
    public class SpringBootTestDefaultTest {
        @Test
        public void test() throws IOException {
            HttpURLConnection conn = (HttpURLConnection) URI.create("http://localhost:8080").toURL().openConnection();
            assertThatThrownBy(() -> conn.connect())
                    .isInstanceOf(ConnectException.class)
                    .hasMessage("接続を拒否されました");
    

    ドキュメントどおりサーバーが起動していないので、接続が拒否されます。

    では、先に進めていきましょう。

    @WebMvcTestを使ってテストを書く

    ドキュメントの順番とはだいぶ異なりますが、最初に@WebMvcTestアノテーションを使ったテストを書くことにします。

    Core Features / Testing / Testing Spring Boot Applications / Auto-configured Spring MVC Tests

    こちらは、Spring Web MVCを使って実装した、特定のControllerが動作するかをテストします。

    @WebMvcTestアノテーションを使用すると、Spring Web MVCを構成するとともに限られたBeanをスキャンします。

  • @Controller
  • @ControllerAdvice
  • @JsonComponent
  • Converter
  • GenericConverter
  • Filter
  • HandlerInterceptor
  • WebMvcConfigurer
  • WebMvcRegistrations
  • HandlerMethodArgumentResolver
  • 通常の@Component@ConfigurationPropertiesといったBeanは、@WebMvcTestアノテーションを使用してもスキャンされません。
    使用したい場合は、@EnableConfigurationPropertiesアノテーションを付与することになります。

    なお、@WebMvcTestアノテーションを使用した時に有効になるAuto Configurationは、以下にまとめられています。

    Test Auto-configuration Annotations

    @WebMvcTestアノテーションを使用して作成したテストは、こちら。

    src/test/java/org/littlewings/spring/webmvc/WebMvcTestTest.java

    package org.littlewings.spring.webmvc;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.hamcrest.Matchers;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
    import org.springframework.boot.test.mock.mockito.MockBean;
    import org.springframework.http.MediaType;
    import org.springframework.test.web.servlet.MockMvc;
    import static org.mockito.BDDMockito.given;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
    @WebMvcTest(EchoController.class)
    public class WebMvcTestTest {
        @Autowired
        MockMvc mvc;
        @MockBean
        MessageDecorationService messageDecorationService;
        @Autowired
        ObjectMapper objectMapper;
        @Test
        public void getTest() throws Exception {
            given(messageDecorationService.decorate("WebMvcTest[get]"))
                    .willReturn("*** WebMvcTest[get] !!***");
            // queryParam
                    .perform(
                            get("/echo/get")
                                    .queryParam("message", "WebMvcTest[get]")
                    .andExpect(status().isOk())
                    .andExpect(
                            content()
                                    .json(
                                            objectMapper.writeValueAsString(
                                                    EchoResponse.create("*** WebMvcTest[get] !!***", "main")
            // querystring
                    .perform(
                            get("/echo/get?message=WebMvcTest[get]")
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("message", Matchers.is("*** WebMvcTest[get] !!***")))
                    .andExpect(jsonPath("threadName", Matchers.is("main")));
        @Test
        public void postTest() throws Exception {
            given(messageDecorationService.decorate("WebMvcTest[post]"))
                    .willReturn("*** WebMvcTest[post] !!***");
                    .perform(
                            post("/echo/post")
                                    .contentType(MediaType.APPLICATION_JSON)
                                    .content(
                                            objectMapper.writeValueAsString(
                                                    EchoRequest.create("WebMvcTest[post]")
                    .andExpect(status().isOk())
                    .andExpect(header().string("content-type", MediaType.APPLICATION_JSON_VALUE))
                    .andExpect(jsonPath("message", Matchers.is("*** WebMvcTest[post] !!***")))
                    .andExpect(jsonPath("threadName", Matchers.is("main")));
    

    テストクラスに@WebMvcTestアノテーションを付与します。

    @WebMvcTest(EchoController.class)
    public class WebMvcTestTest {
    

    ドキュメントに習ってControllerを@WebMvcTestアノテーションに指定していますが、実は指定しなくても動きます。
    スキャン対象に@Controllerが入っているからですね。

    Controllerが依存するBeanは、モックにします。

        @MockBean
        MessageDecorationService messageDecorationService;
    

    というか、最初に書いたようにスキャンされるBeanが限定されているので、今回のように別のBeanに依存しているControllerはモックで
    依存関係をなんとかするなりBeanを登録するなりしないと動作しません。

    もし今回のソースコードからモックに関するコードを削除すると、作成したRestControllerの依存関係が解決できずにテストに失敗します。

    Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'echoController' defined in file [/path/to/webmvc-testing/target/classes/org/littlewings/spring/webmvc/EchoController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.littlewings.spring.webmvc.MessageDecorationService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
        at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800)
        at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1372)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1222)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:953)
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:740)
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:415)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:303)
        at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:136)
        at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
        at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
        ... 72 more
    Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.littlewings.spring.webmvc.MessageDecorationService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1799)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1355)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1309)
        at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887)
        at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
        ... 90 more

    テストにはMockMvcを使用します。

        @Autowired
        MockMvc mvc;
    

    MockMvc自体については、こちらを参照。

    Testing / Integration Testing / MockMvc

    今回は、最初に依存するServiceのモックを設定して、それからMockMvcを使ってテストしています。

        @Test
        public void getTest() throws Exception {
            given(messageDecorationService.decorate("WebMvcTest[get]"))
                    .willReturn("*** WebMvcTest[get] !!***");
            // queryParam
                    .perform(
                            get("/echo/get")
                                    .queryParam("message", "WebMvcTest[get]")
                    .andExpect(status().isOk())
                    .andExpect(
                            content()
                                    .json(
                                            objectMapper.writeValueAsString(
                                                    EchoResponse.create("*** WebMvcTest[get] !!***", "main")
            // querystring
                    .perform(
                            get("/echo/get?message=WebMvcTest[get]")
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("message", Matchers.is("*** WebMvcTest[get] !!***")))
                    .andExpect(jsonPath("threadName", Matchers.is("main")));
    

    今回は、なんとなくQueryStringをqueryParamで設定したり、直接URLに付けたりしてみました。また、JSONレスポンスのアサーション方法も
    分けています。

    アサーション結果を見るとわかるのですが、今回のテストではRestControllerはmainスレッドで動作していますね。

                    .andExpect(jsonPath("message", Matchers.is("*** WebMvcTest[get] !!***")))
                    .andExpect(jsonPath("threadName", Matchers.is("main")));
    

    RestControllerのメソッド呼び出し時のスタックトレースはこちら。

    java.lang.Exception: Stack trace
            at java.base/java.lang.Thread.dumpStack(Thread.java:1380)
            at org.littlewings.spring.webmvc.EchoController.get(EchoController.java:23)
            at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
            at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
            at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
            at java.base/java.lang.reflect.Method.invoke(Method.java:568)
            at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
            at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
            at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
            at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
            at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
            at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
            at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067)
            at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
            at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
            at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
            at javax.servlet.http.HttpServlet.service(HttpServlet.java:655)
            at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
            at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:72)
            at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
            at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167)
            at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
            at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
            at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
            at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
            at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
            at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
            at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
            at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
            at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
            at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
            at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:199)
            at org.littlewings.spring.webmvc.WebMvcTestTest.getTest(WebMvcTestTest.java:35)
    

    この少し後に書くモック環境でのテストについて書いた内容を見るとわかりますが、@WebMvcTestアノテーションを使った時もモック環境で
    動作していることになります。

    MockMvcについて

    MockMvcについて、もう少し深堀してみましょう。

    MockMvcにはスタンドアロンモードとSpringのConfigurationを使ったモードの2つのセットアップ方法があります。

    Testing / Integration Testing / MockMvc / Setup Choices

    Spring Bootでは、SpringのConfigurationを使ったモードでセットアップするようです。

    https://github.com/spring-projects/spring-boot/blob/v2.6.6/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java#L78-L87

    https://github.com/spring-projects/spring-boot/blob/v2.6.6/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java#L95-L99

    MockMvcのセットアップ自体は完了しているので、あとはリクエストを設定したり、レスポンスをアサーションしたりといった感じの
    使い方になります。

    Testing / Integration Testing / MockMvc / Performing Requests

    Testing / Integration Testing / MockMvc / Defining Expectations

    今回は通常のControllerで使いたくなるようなフォワード先やモデルのアサーションは行いませんが、サンプルがあるのでこちらを
    参考にするとよいでしょう。

    https://github.com/spring-projects/spring-framework/tree/v5.3.18/spring-test/src/test/java/org/springframework/test/web/servlet/samples

    Servlet Filterを登録する機能もありそうですが

    Testing / Integration Testing / MockMvc / Filter Registrations

    こちらは、@WebMvcTestアノテーションの属性で表現することになりそうです。

    WebMvcTest (Spring Boot 2.6.6 API)

    ドキュメントを見ると、@AutoConfigureMockMvcアノテーションを使うとよい、とは書かれていますが。

    Core Features / Testing / Testing Spring Boot Applications / Auto-configured Spring MVC Tests

    また、MockMvcとE2Eテストの比較についてもドキュメントに書かれています。

    Testing / Integration Testing / MockMvc / MockMvc vs End-to-End Tests

    MockMvcServletのモック実装に基づいて構築されていて、実際のサーブレットコンテナと比べると異なる部分もあります。

    jsessoinid Cookieがない、フォワードやリダイレクト、サーブレットの例外ハンドリングは行われず、(Spring Bootではあまり関係ないですが)
    JSPレンダリングも行われません。
    ※Theymeleafなどのテンプレートエンジンでのレンダリングや、JSONXMLなどでのレスポンスは行えるようです

    MockMvcを使用したテストではControllerに依存するBeanをモック化したりして、Webレイヤーに限定したテストがしやすくなるところが
    ポイントです。SpringとしてはMockMvcの利用もインテグレーションテストに位置づけていますが、単体テストに近いものではあります。

    このため、E2Eテストのようなブラックボックス的なテストと異なり、MockMvcではHandler(Controller)が使われたこと、例外処理で
    HandlerExceptionResolverが使われたこと、モデルの属性やバインディングエラーなどを確認できます。

    このような特性を踏まえつつ、どのテストを使うかを決めていくことになるんでしょうね。

    モック環境でテストする

    次は、モック環境でのテストを行います。

    Core Features / Testing / Testing Spring Boot Applications / Testing with a mock environment

    具体的には、@SpringBootTestアノテーション@AutoConfigureMockMvcアノテーションの組み合わせになります。

    作成したテストコードはこちら。

    src/test/java/org/littlewings/spring/webmvc/AutoConfigureMockMvcTest.java

    package org.littlewings.spring.webmvc;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.hamcrest.Matchers;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.http.MediaType;
    import org.springframework.test.web.servlet.MockMvc;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
    @SpringBootTest
    @AutoConfigureMockMvc
    public class AutoConfigureMockMvcTest {
        @Test
        public void getTest(@Autowired MockMvc mvc) throws Exception {
                    .perform(
                            get("/echo/get")
                                    .queryParam("message", "MockMvc[get]")
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("message", Matchers.is("★★★ MockMvc[get] !!★★★")))
                    .andExpect(jsonPath("threadName", Matchers.is("main")));
        @Test
        public void postTest(@Autowired MockMvc mvc, @Autowired ObjectMapper objectMapper) throws Exception {
                    .perform(
                            post("/echo/post")
                                    .contentType(MediaType.APPLICATION_JSON)
                                    .content(
                                            objectMapper.writeValueAsString(
                                                    EchoRequest.create("MockMvc[post]")
                    .andExpect(status().isOk())
                    .andExpect(header().string("content-type", MediaType.APPLICATION_JSON_VALUE))
                    .andExpect(jsonPath("message", Matchers.is("★★★ MockMvc[post] !!★★★")))
                    .andExpect(jsonPath("threadName", Matchers.is("main")));
    

    @WebMvcTestアノテーションを使った時と同じようにMockMvcを使ったテストになりますが、先ほどとの違いはRestControllerが
    依存するBeanをモック化していないことですね。

    またテストクラスに付与するアノテーションで、明示的なControllerの指定もなくなりました。

    @SpringBootTest
    @AutoConfigureMockMvc
    public class AutoConfigureMockMvcTest {
    

    RestControllerのメソッド呼び出し時のスタックトレースは、こちら。

    java.lang.Exception: Stack trace
        at java.base/java.lang.Thread.dumpStack(Thread.java:1380)
        at org.littlewings.spring.webmvc.EchoController.get(EchoController.java:23)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:655)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
        at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:72)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
        at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167)
        at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
        at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
        at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
        at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
        at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:199)
        at org.littlewings.spring.webmvc.AutoConfigureMockMvcTest.getTest(AutoConfigureMockMvcTest.java:22)
    

    よく見ると、@WebMvcTestアノテーションを使った時のスタックトレースとまったく同じです。

    そして、スレッド名もmainであることが確認できます。

                    .andExpect(jsonPath("message", Matchers.is("★★★ MockMvc[post] !!★★★")))
                    .andExpect(jsonPath("threadName", Matchers.is("main")));
    

    ということは、この2つのテスト方法では、同じモック環境で動作していることになりますね。違いはBeanのスキャン範囲といったところ
    でしょうか(@SpringBootTestアノテーションを使っている差かと)。

    ちなみに、このテストの中に@MockBeanアノテーションを使ったコードを追加することで、依存するBeanをモック化することは
    ふつうにできます。

        @MockBean
        MessageDecorationService messageDecorationService;
    

    組み込みサーバーを起動してテストする

    最後は、組み込みサーバーを起動してテストを行います。

    Core Features / Testing / Testing Spring Boot Applications / Testing with a running server

    こちらは、@SpringBootTestアノテーションwebEnvironment属性にRANDOM_PORTまたはDEFINED_PORTを指定することで
    実現できます。今回は、RANDOM_PORTを使用します。

    作成したテストはこちら。

    src/test/java/org/littlewings/spring/webmvc/SpringBootTestWebEnvironmentTest.java

    package org.littlewings.spring.webmvc;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.http.MediaType;
    import org.springframework.test.web.reactive.server.WebTestClient;
    import static org.assertj.core.api.Assertions.assertThat;
    @SpringBootTest(
            webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
            properties = "server.tomcat.threads.max=1"
    public class SpringBootTestWebEnvironmentTest {
        @Test
        public void getTest(@Autowired WebTestClient webClient) {
            webClient
                    .get()
                    .uri(uriBuilder -> uriBuilder
                            .path("/echo/get")
                            .queryParam("message", "WebEnvironment Test[get]")
                            .build()
                    .exchange()
                    .expectStatus().isOk()
                    .expectBody(EchoResponse.class)
                    .consumeWith(result -> {
                        EchoResponse response = result.getResponseBody();
                        assertThat(response.getMessage()).isEqualTo("★★★ WebEnvironment Test[get] !!★★★");
                        assertThat(response.getThreadName()).isEqualTo("http-nio-auto-1-exec-1");
        @Test
        public void postTest(@Autowired WebTestClient webClient) {
            webClient
                    .post()
                    .uri("/echo/post")
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyValue(EchoRequest.create("WebEnvironment Test[post]"))
                    .exchange()
                    .expectStatus().isOk()
                    .expectBody(EchoResponse.class)
                    .consumeWith(result -> {
                        EchoResponse response = result.getResponseBody();
                        assertThat(response.getMessage()).isEqualTo("★★★ WebEnvironment Test[post] !!★★★");
                        assertThat(response.getThreadName()).isEqualTo("http-nio-auto-1-exec-1");
    

    @SpringBootTestアノテーションwebEnvironment属性はRANDOM_PORTとし、またRestControllerがスレッド名を返すので
    固定になるように組み込みTomcatのスレッドプールは1にしました。

    @SpringBootTest(
            webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
            properties = "server.tomcat.threads.max=1"
    public class SpringBootTestWebEnvironmentTest {
    

    テストには、WebTestClientを使うようです。

        @Test
        public void getTest(@Autowired WebTestClient webClient) {
            webClient
                    .get()
                    .uri(uriBuilder -> uriBuilder
                            .path("/echo/get")
                            .queryParam("message", "WebEnvironment Test[get]")
                            .build()
                    .exchange()
                    .expectStatus().isOk()
                    .expectBody(EchoResponse.class)
                    .consumeWith(result -> {
                        EchoResponse response = result.getResponseBody();
                        assertThat(response.getMessage()).isEqualTo("★★★ WebEnvironment Test[get] !!★★★");
                        assertThat(response.getThreadName()).isEqualTo("http-nio-auto-1-exec-1");
    

    このために、依存関係にspring-boot-starter-webfluxを追加しています。

    Core Features / Testing / Testing Spring Boot Applications / Testing with a running server

    Testing / Integration Testing / WebTestClient

    ちなみに、WebTestClientはモック環境でも利用できるようです。

    Spring WebFluxを依存関係に追加したくない場合は、TestRestTemplateを使ってテストしてもOKです。

    RestController呼び出し時のスタックトレースはこちら。

    java.lang.Exception: Stack trace
        at java.base/java.lang.Thread.dumpStack(Thread.java:1380)
        at org.littlewings.spring.webmvc.EchoController.get(EchoController.java:23)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:655)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:889)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.base/java.lang.Thread.run(Thread.java:833)

    Tomcatのスレッドプール内で動作していることがわかりますね。

    また、アサーション時に確認していたスレッド名も、mainではなくなっていました。

                    .consumeWith(result -> {
                        EchoResponse response = result.getResponseBody();
                        assertThat(response.getMessage()).isEqualTo("★★★ WebEnvironment Test[get] !!★★★");
                        assertThat(response.getThreadName()).isEqualTo("http-nio-auto-1-exec-1");
    

    Beanのモック化も行わなず、Spring Web MVCアプリケーションとして完全な形で動作します。E2Eテストといった用途のイメージで
    使うことになるんでしょうね。

    これでひととおりSpring Web MVCのテスト方法の確認はできた感じでしょうか。

    オマケ:モックを使ったBeanのテスト

    ここまでSpring Web MVCのテストを見てきましたが、ふつうのBeanのテストのパターンを完全に飛ばしています。

    軽くやっておきましょう。単体テストな感じであればインスタンスnewしても良いと思うのですが、今回はモックを使ってみます。

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

    まずは、Serviceをもうひとつ追加しましょう。

    src/main/java/org/littlewings/spring/webmvc/WordsJoinService.java

    package org.littlewings.spring.webmvc;
    import java.util.Arrays;
    import java.util.stream.Collectors;
    import org.springframework.stereotype.Service;
    @Service
    public class WordsJoinService {
        MessageDecorationService messageDecorationService;
        public WordsJoinService(MessageDecorationService messageDecorationService) {
            this.messageDecorationService = messageDecorationService;
        public String join(String... words) {
            return messageDecorationService.decorate(Arrays.stream(words).collect(Collectors.joining(" ")));
    

    このServiceでは、最初に作成したServiceを使っています。

    テストはこちら。

    src/test/java/org/littlewings/spring/webmvc/MockingBeansTest.java

    package org.littlewings.spring.webmvc;
    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 static org.hamcrest.MatcherAssert.assertThat;
    import static org.hamcrest.Matchers.is;
    import static org.mockito.BDDMockito.given;
    @SpringBootTest
    public class MockingBeansTest {
        @Autowired
        WordsJoinService wordsJoinService;
        @MockBean
        MessageDecorationService messageDecorationService;
        @Test
        public void serviceTest() {
            given(messageDecorationService.decorate("Hello World"))
                    .willReturn("*** Hello World !!***");
            assertThat(wordsJoinService.join("Hello", "World"), is("*** Hello World !!***"));
    

    テストクラスは@SpringBootTestアノテーションを使って作成して、テスト対象のBeanを@Autowiredしつつ、依存するBeanは
    @MockBeanアノテーションでモック化すればOKです。

    Spring Web MVCで作成したアプリケーションのテスト方法を見てみました。

    あまり理解できていなかったので、時間を取って今回整理してみて良かったかなぁと思います。