33
37

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Javaでprivateメソッドのテストとそのメソッドをパーシャルモックする方法

Last updated at Posted at 2017-10-23

概要

SpringBootでテスト書いてたらprivateメソッドのテストのやり方と、privateメソッドのモックのやり方がわからず詰まったのでまとめる。
Mockitoだけではprivateメソッドのモックができないようなので、PowerMockを併用する。

環境

  • Java 8
  • SpringBoot 1.5.7.RELEASE

テスト対象クラス

テスト対象クラスを適当に用意する。HelloServiceクラスとする。

import org.springframework.beans.factory.annotation.Autowired;

public class HelloService {

    @Autowired
    private HelloRepository repository;

    public String getFromRepo() {
        return repository.getData();
    }

    public String methodPublic(String arg) {
        return "method public: " + methodPrivate(arg);
    }

    private String methodPrivate(String arg) {
        return "method private, arg: " + arg;
    }
}

HelloServiceクラスが依存するクラスを適当に作る。

public class HelloRepository {

    public String getData() {
        return "HelloRepository.getData";
    }
}

解説

privateメソッドのテスト

ここでは、上記テスト対象クラスHelloServiceクラスのprivateメソッドであるmethodPrivateメソッドのテスト方法について解説する。

やってみる

import org.junit.Before;
import org.junit.Test;
import java.lang.reflect.Method;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

public class HelloServiceTest {

    private HelloService service;

    @Before
    public void setUp() {
        service = new HelloService();
    }

    @Test
    public void methodPrivateのテスト() throws Exception {
        Method method = HelloService.class.getDeclaredMethod("methodPrivate", String.class);
        method.setAccessible(true);
        String actual = (String)method.invoke(service, "hoge");
        assertThat(actual, is("method private, arg: hoge"));
    }
}

解説

  • テスト対象クラス.class.getDeclaredMethod(テスト対象メソッド名、引数の型)とすると、テスト対象クラスのメソッドインスタンスが取得できる
  • method.setAccessible(true)とするとprivateメソッドでも呼べるようになる
  • method.invoke(引数)でそのメソッドが呼べる。返り値がObjectになるので、キャストする。
    • Unchecked castって警告がでたら、上の行でに@SuppressWarnings("unchecked")をつけると消える

privateメソッドをパーシャルモックする

ここでは、上記テスト対象クラスHelloServiceクラスのmethodPublicメソッドをテストするにあたって、中で呼んでいるprivateメソッドmethodPrivateをパーシャルモック化する方法について解説する

やってみる


PowerMockを使うのでbuild.gradleに以下を追加(maven使っている場合は適当に読み替えてください)

dependencies {
    testCompile('org.powermock:powermock-module-junit4:1.6.2')
    testCompile('org.powermock:powermock-api-mockito:1.7.3')
}

テストを書く

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import java.lang.reflect.Method;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.times;
import static org.powermock.api.mockito.PowerMockito.verifyPrivate;
import static org.powermock.api.mockito.PowerMockito.when;
import static org.powermock.api.mockito.PowerMockito.spy;

@RunWith(PowerMockRunner.class)
@PrepareForTest(HelloService.class)
public class HelloServiceTest {

    private HelloService service;

    @Before
    public void setUp() {
        service = spy(new HelloService());
    }

    @Test
    public void methodPublicのテスト() throws Exception {
        when(service, "methodPrivate", "fuga").thenReturn("hoge");
        String actual = service.methodPublic("fuga");
        verifyPrivate(service, times(1)).invoke("methodPrivate", "fuga");

        assertThat(actual, is("method public: " + "hoge"));
    }
}

解説

  • クラスにアノテーションを2つ追加。@RunWith(PowerMockRunner.class)@PrepareForTest(テスト対象.class)
  • パーシャルモックを作るために、setUp()の中でspy(テスト対象クラスのインスタンス)を呼んでいる。ここでMockitoのspyではなく、PowerMockのspy(org.powermock.api.mockito.PowerMockito.spy)にするのがポイント
  • when()についても同様で、PowerMock のwhenを使う。org.powermock.api.mockito.PowerMockito.whenを使っている。
  • これでprivateだろうとpublicだろうと気にせずパーシャルモックが作れる
  • 呼ばれたことの検証には、verifyPrivateを使う。
  • whenやverifyの使い勝手はMockitoと同じ感じっぽい

Mockitoのモックと併用する

getFromRepoメソッドのテストを追加する。

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.powermock.api.mockito.PowerMockito.verifyPrivate;
import static org.powermock.api.mockito.PowerMockito.when;
import static org.powermock.api.mockito.PowerMockito.spy;

@RunWith(PowerMockRunner.class)
@PrepareForTest(HelloService.class)
public class HelloServiceTest {

    @Mock
    private HelloRepository repository;

    @InjectMocks
    private HelloService service;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        service = spy(service);
    }

    @Test
    public void getFromRepoのテスト() {
        when(repository.getData()).thenReturn("hoge");
        assertThat(service.getFromRepo(), is("hoge"));

        verify(service, times(1)).getFromRepo();
    }

    @Test
    public void methodPublicのテスト() throws Exception { // 上と同じ }

    @Test
    public void methodPrivateのテスト() throws Exception { // 上と同じ }
}

解説

  • 依存しているHelloRepositoryをモックにしている。
  • モックにしたいフィールドに@Mockアノテーションをつけて、モックを注入したいフィールドに@InjectMocksアノテーションをつける
  • setUp()の中でMockitoAnnotations.initMocks(this);を呼んであげると、よしなにモックを注入してくれるみたい
  • 上記のPowerMockと共存するか心配だったけど、単純にinitMocksの後にservice = spy(service);ってやってあげたら良い感じに動いた。

所感

PowerMockが便利だった。
privateじゃないメソッドを(今回でいうHelloRepository的なやつ)モックする時はMockitoを使っていて、@Mockとか@InjectMocks便利だなーと思っていた。
privateメソッドをモックするのにPowerMockを使おうとしたときに、やり方がガラッと変わったら混乱しそうだと思ったけど、同じ感じの書き方で書けそうで安心。

参考

33
37
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
33
37

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?