LoginSignup
7
6

More than 3 years have passed since last update.

S3などのデータストアへのアクセス機能をモックとすることでデータストアなしでユニットテストする手法( Java, Mockito )

Last updated at Posted at 2020-04-10

概要

データストアにアクセスすることなくテストを可能とするポイントを紹介します。

  1. データストア( 今回はS3 )にアクセスするオブジェクトを、外部からテスト対象クラスに注入可能としておく
  2. テスト時は、データストアにアクセスしないモックを注入する

動作確認可能なソースコードは loftkun/spring-boot-s3-mock-example にあります。

テスト対象クラス

テスト対象のクラスです。S3にアクセスするclientを外部から注入できるようにしています。
( この例ではコンストラクタで受け取っています。 )

このようにデータストアにアクセスするオブジェクトを、外部からテスト対象クラスに注入できるように実装しておきます。これがいわゆる依存性注入 ( DI : Dependency Injection ) です。

のちほど紹介するテストでは、データストアにアクセスしないモックを注入します。
これにより、テスト用データストアの準備が不要となります。

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.Bucket;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestTargetClass {

    private static final Logger logger = LoggerFactory.getLogger(TestTargetClass.class);

    private AmazonS3 client = null;

    public TestTargetClass(AmazonS3 client) {
        this.client = client;
    }

    public boolean testTargetMethod(String bucketName){
        if (client.doesBucketExistV2(bucketName)) {
            logger.info("doesBucketExistV2");
            return false;
        }
        Bucket bucket = client.createBucket(bucketName);
        logger.info(bucket.toString());
        logger.info(client.getBucketLocation(bucketName));
        return true;
    }
}

テスト対象のメソッドはtestTargetMethod()です。S3にバケットを作成する単純なメソッドです。
このメソッドのテストケースは以下2つです。

  1. バケットが存在しなければ作成し、作成したバケットの情報をログに出力する。
  2. バケットが既に存在する場合はその旨をログに出力し、バケットを作成しない

続いて、これらのテストケースをモックを用いて検証する手法を紹介します。

テストクラス

テストメソッドは以下2つを実装しています。

テストケース メソッド
1 create_bucket()
2 not_create_bucket()

モックフレームワークは Mockito を利用しました。
以下のようにしてclientのモックを作成しています。

AmazonS3 client = mock(AmazonS3.class);

作成したモックをテスト対象のオブジェクトに注入します。
今回はコンストラクタで注入していますが、setterでも構いません。
フレームワークが提供するDI機能( DIコンテナ )を活用するのも良いです。

TestTargetClass testTarget = new TestTargetClass(client);

これにより、テストのためにデータストアを準備する必要がなくなります。

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.Bucket;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestTargetClassTest {

    private static final Logger logger = LoggerFactory.getLogger(TestTargetClassTest.class);

    @Test
    void create_bucket() {

        String bucketName = "testBucketName";
        String bucketLocation = "testBucketLocation";

        // define mock
        Bucket bucket = mock(Bucket.class);
        when(bucket.getName()).thenReturn(bucketName);
        AmazonS3 client = mock(AmazonS3.class);
        when(client.doesBucketExistV2(bucketName)).thenReturn(false);
        when(client.createBucket(bucketName)).thenReturn(bucket);
        when(client.getBucketLocation(bucketName)).thenReturn(bucketLocation);

        // test target
        TestTargetClass testTarget = new TestTargetClass(client);
        boolean result = testTarget.testTargetMethod(bucketName);

        // verify
        verify(client, times(1)).doesBucketExistV2(bucketName);
        verify(client, times(1)).createBucket(bucketName);
        verify(client, times(1)).getBucketLocation(bucketName);
        assertEquals(true, result);
    }

    @Test
    void not_create_bucket() {

        String bucketName = "testBucketName";

        // define mock
        Bucket bucket = mock(Bucket.class);
        when(bucket.getName()).thenReturn(bucketName);
        AmazonS3 client = mock(AmazonS3.class);
        when(client.doesBucketExistV2(bucketName)).thenReturn(true);

        // test target
        TestTargetClass testTarget = new TestTargetClass(client);
        boolean result = testTarget.testTargetMethod(bucketName);

        // verify
        verify(client, times(1)).doesBucketExistV2(bucketName);
        verify(client, times(0)).createBucket(bucketName);
        verify(client, times(0)).getBucketLocation(bucketName);
        assertEquals(false, result);
    }
}

テストケース1

モックの定義

以下のようにしてdoesBucketExistV2()が呼び出された場合、falseを返すモックとしています。
これにより同名の既存バケットが存在しないという条件を与えることにより、テストケース1をテストできるようにしています。

when(client.doesBucketExistV2(bucketName)).thenReturn(false);

検証

以下を検証しています。

  • 以下メソッドが1回呼び出されること
    • doesBucketExistV2()
      • 既存バケットの存在チェックを行なっていること
    • createBucket()
      • バケットを作成すること
    • getBucketLocation()
      • 作成したバケットの情報を取得していること
  • 戻り値がtrueであること
    • 今回は例ということで期待値をtrueとしています

テストケース2

モックの定義

以下のようにしてdoesBucketExistV2()が呼び出された場合、trueを返すモックとしています。
これにより同名の既存バケットが存在するという条件を与えることにより、テストケース2をテストできるようにしています。

when(client.doesBucketExistV2(bucketName)).thenReturn(true);

検証

以下を検証しています。

  • 以下メソッドが1回呼び出されること
    • doesBucketExistV2()
      • 既存バケットの存在チェックを行なっていること
  • 以下メソッドは呼び出されないこと
    • createBucket()
      • バケットを作成しないこと
    • getBucketLocation()
      • バケットの情報を取得していないこと
  • 戻り値がfalseであること
    • 今回は例ということで期待値をfalseとしています

まとめ

データストアに実際にアクセスすることなくテストを可能とするポイントを紹介しました。

  • データストアにアクセスするオブジェクトをDI可能としておく
  • テスト時にはモックをDIする

データストアに依存する条件をモックとして与えることにより、テスト用のデータストアの準備が不要となります。
本記事がテスト手法の参考になりましたら幸いです。

7
6
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
7
6