LoginSignup
1
2

More than 1 year has passed since last update.

SpringBootでsmall test JUnit5 × Mockito ver

Last updated at Posted at 2021-04-26

はじめに

SpringBootでテストを、となった際にテストサイズを考えsmall testのUnitテストを実装したいという場面があるが、small testをどのように実装するのかについて記載した記事をあまり見かけないので、自身の備忘録としても残しておく。
※ググると大抵dependencyにspring test系を持たせているものが見つかるが、ミニマムのunit testを実装する上ではそれらは不要。

この記事を読むと分かる事は以下。

small testとは

そもそもsmall testとはどういうもの?という話になるが、small testとは以下のようなテストとして定義できる。

  • ネットワークへアクセスしない
  • データベースへアクセスしない
  • ファイルシステムへアクセスしない
  • 外部システムへアクセスしない
  • シングルスレッドで動作する
  • システムプロパティへのアクセスをしない

カバレッジの話

上記のsmall testの概念に加えて、ソースコードのテストにおいて切り離せない話でカバレッジがあるが、
いずれのカバレッジでもsmall testは実現できるので、ここではどのカバレッジで実装するかは扱わない。

JUnit5×Mockitoでsmall test

SpringBootにおけるMockitoでのsmall testを導入する際の依存関係(pom.xmlの内容)

プロジェクト(パッケージ)管理ツールにmavenを使っている場合、pom.xmlは以下のようになる。
※色々省略しているが、samll testであれば基本的にspring-boot-starter-testなどの依存は不要。

pom.xml
<project xmlns="・・・">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
    </parent>

    <properties>
        <junit-jupiter.version>5.7.1</junit-jupiter.version>
        <mockito.version>3.7.7</mockito.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-web</artifactId>
        </dependency>
        <!-- jdbc, json, lombokなどなど -->
        <dependency>
            <groupId>com.googlecode.json-simple</groupId>
            <artifactId>json-simple</artifactId>
            <version>1.1.1</version>
            <exclusions>
                <exclusion>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-inline</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

ここでは

  • static methodのMock化が行えるようにmockito-inlineを依存に追加
  • JUnitの依存を持つjson-simpleのJUnit依存をexclusionに指定し除外
    ※自動的に依存が追加されるような場合はexclusionを使い依存を明示的に除外する

をしている。
ここで注意として、MockitoであればPowerMockを使いたい場面もあるかもしれないが、現時点でJunit5では使えないよう・・・

DI(@Autowired)されているフィールドの扱い方などテストコードの基本的な書き方

まずMockitoを使うため、@ExtendWithMockitoExtension.classを宣言し継承する。
SpringでDI(@Autowired)されている各フィールドは@Mockでmock化する。
また、それらをtest対象のクラスにinjectするために、test対象クラスに対しては@InjectMocksを記載しmockを注入する。
基本的なパターンとしては以下のような実装になる。
※Mockitoの使い方・構文についてはここを参照。

SampleTest.java
// 省略
@ExtendWith(MockitoExtension.class)
class SampleTest {

  @Mock
  private Hoge mockHoge;
  @Mock
  private Fuga mockFuga;

  @InjectMocks
  private Sample testClass;

  @Test
  void testFoo() {
    doReturn("hoge").when(mockHoge).getHoge();
    assertEquals("hoge", testClass.foo());
  }
  // 省略
}

細かいテストの記述方法やテクニック

static methodのmock化の方法

MockedStaticを利用する。

MockStaticSampleTest.java
// 省略
@ExtendWith(MockitoExtension.class)
class MockStaticSampleTest {

  @Mock
  private Hoge mockHoge;

  @InjectMocks
  private MockStaticSample testClass;

  @Test
  void testGetCompleteDate() {
    try (MockedStatic<DateUtil> mocked = mockStatic(DateUtil.class)) {
      mocked.when(() -> DateUtil.getNumbersOnlyFormat(anyString()).thenReturn("19700101");                             
      assertEquals("19700101", testClass.getCompleteDate());
      mocked.verify(() -> DateUtil.getNumbersOnlyFormat(anyString());
    }  
  }
  // 省略
}

テスト対象のメソッドで、自身のクラスのメソッド(private以外)を呼び出すような場合にテストを簡単にするテクニック(spyの利用)

以下のようにテスト対象のメソッド内で、自身の他のメソッド(private以外)が呼ばれるような場合、spyを使うと一部だけmock化ができテストを書きやすくなる。
具体的には、自身のメソッドを呼び出しその戻り値を使って分岐をするようなメソッドをテストする際に、カバレッジをC2など複雑なものを採用すると、自身のメソッドの返り値まで考えて複雑な実装しなければならず大変になるが、それを簡単にする事ができる。

PrivateContainSample.java
// 省略
class PrivateContainSample {

  SampleContentsDto foo() {
    SampleDto dto = generate(commonDto);
    // 何かの処理
  }

  SampleDto generate(SampleCommonDto dot) {
    // 何かの処理
  } 
  // 省略
}
PrivateContainSampleTest.java
// 省略
@ExtendWith(MockitoExtension.class)
class MockStaticSampleTest {

  @Mock
  private Hoge mockHoge;

  @InjectMocks
  private PrivateContainSample testClass;
  private PrivateContainSample sptTestClass;

  @BeforeEach
  void setUp() {
    spyTest = spy(testClass);
  }

  @Test
  void testFoo() {
    doReturn(new SampleDto()).when(sptTestClass).generate(any(SampleCommonDto.class));
    assertNotNull(spyTestClass.foo());
  }
  // 省略
}

spyで記述した部分(generate)だけがmock化されるので、fooはmock化されず通常通りテストが実施できる。

共通のmockの定義方法・引数が可変長のもののmock化方法

テスト対象のクラス全体で使われているメソッドで、1つの共通の戻り値があればいいような場合は、@BeforeEachでmock化してしまうと便利。
また、可変長引数の場合はMockito.<String[]>any()のように記述する。

SampleTest.java
// 省略
@ExtendWith(MockitoExtension.class)
class SampleTest {

  @Mock
  private Hoge mockHoge;
  @Mock
  private CommonMessage mockCommonMessage;

  @InjectMocks
  private Sample testClass;

  @BeforeEach
  void setUp() {
    doReturn(new Message()).when(mockCommonMessage).getMessage(Mockito.<String[]>any());
  }

  @Test
  void testFoo() {
    // 何かのテスト
  }
  // 省略
}

MockMultipartFileを使ったmock化の方法

MockMultipartFile.java
/**
 * Create MockMultipartFile used for JUnit test
 *
 * @param fileName File name used for testing
 * @return MockMultipartFile
 */
private MockMultipartFile createTestFile(String fileName) throws Exception {
  MockMultipartFile multipartFile;

  String path = new File(".").getAbsoluteFile().getParent();
  File file = new File(path + "/src/test/resources/hogehoge/" + fileName + ".xlsx");

  try (FileInputStream input = new FileInputStream(file)) {
    multipartFile = new MockMultipartFile("file", file.getName(), CONTENT_TYPE, IOUtils.toByteArray(input));
  }
  return multipartFile;
}

参考文献

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2