1概要

Java 8では、ラムダやストリームなど、さまざまな新しい素晴らしい機能が導入されました。そして当然、Mockitoはhttps://github.com/mockito/mockito/wiki/What’s-new-in-Mockito-2[第2のメジャーバージョン]でこれらの最近の革新を利用しました。

この記事では、この強力な組み合わせが提供するものすべてを探ります。

** 2デフォルトのメソッドを使ったモックインタフェース

Java 8以降では、インターフェイスにメソッド実装を記述できるようになりました。これは素晴らしい新機能かもしれませんが、その言語への導入は、その概念以来Javaの一部であった強い概念に違反していました。

Mockitoバージョン1はこの変更の準備ができていませんでした。基本的に、インターフェースから実際のメソッドを呼び出すように要求することができなかったためです。

2つのメソッド宣言を持つインターフェースがあると想像してみてください。最初のものは私たちが慣れ親しんできた昔ながらのメソッドシグネチャで、もう1つはまったく新しい

default

メソッドです。

public interface JobService {

    Optional<JobPosition> findCurrentJobPosition(Person person);

    default boolean assignJobPosition(Person person, JobPosition jobPosition) {
        if(!findCurrentJobPosition(person).isPresent()) {
            person.setCurrentJobPosition(jobPosition);

            return true;
        } else {
            return false;
        }
    }
}


assignJobPosition()


default

メソッドが、未実装の

findCurrentJobPosition()

メソッドを呼び出していることに注意してください。

さて、実際の

findCurrentJobPosition()の実装を書かずに、

assignJobPosition()の実装をテストしたいとしましょう。

モックアップされた

JobServiceを作成するだけで、実装されていないメソッドの呼び出しから既知の値を返し、

assignJobPosition()__が呼び出されたときに実際のメソッドを呼び出すようにMockitoに指示できます。

public class JobServiceUnitTest {

    @Mock
    private JobService jobService;

    @Test
    public void givenDefaultMethod__whenCallRealMethod__thenNoExceptionIsRaised() {
        Person person = new Person();

        when(jobService.findCurrentJobPosition(person))
              .thenReturn(Optional.of(new JobPosition()));

        doCallRealMethod().when(jobService)
          .assignJobPosition(
            Mockito.any(Person.class),
            Mockito.any(JobPosition.class)
        );

        assertFalse(jobService.assignJobPosition(person, new JobPosition()));
    }
}

これは完全に合理的であり、インターフェイスの代わりに抽象クラスを使用していたならばそれはちょうどうまくいくでしょう。

しかし、Mockito 1の内部動作は、この構造の準備ができていませんでした。このコードをMockito 2より前のバージョン2で実行すると、このエラーはよくわかります。

org.mockito.exceptions.base.MockitoException:
Cannot call a real method on java interface. The interface does not have any implementation!
Calling real methods is only possible when mocking concrete classes.

Mockitoはその仕事をしていて、この操作はJava 8より前では考えられなかったので、インターフェース上で実際のメソッドを呼び出すことはできないと言っています。

良いニュースは、使用しているMockitoのバージョンを変更するだけで、このエラーを解決できることです。たとえば、Mavenを使用して、バージョン2.7.5を使用できます(最新のMockitoバージョンはhttps://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.mockito%22%にあります)。 20AND 20a%3A%22 mockito-core%22[ここ]):

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.7.5</version>
    <scope>test</scope>
</dependency>

コードを変更する必要はありません。次回テストを実行するときには、エラーは発生しなくなります。


3

Optional



Stream


のデフォルト値を返す



https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html(オプション)


および


Stream


は、その他のJava 8の新しい追加機能です。 2つのクラスの1つの類似点は、どちらも空のオブジェクトを表す特別なタイプの値を持つことです。この空のオブジェクトを使用すると、これまでのような一般的な__httpsを回避するのが簡単になります。


3.1.

Optional


の例

前のセクションで説明した

JobService

をインジェクトし、

JobService#findCurrentJobPosition()

を呼び出すメソッドがあるサービスを考えます。

public class UnemploymentServiceImpl implements UnemploymentService {

    private JobService jobService;

    public UnemploymentServiceImpl(JobService jobService) {
        this.jobService = jobService;
    }

    @Override
    public boolean personIsEntitledToUnemploymentSupport(Person person) {
        Optional<JobPosition> optional = jobService.findCurrentJobPosition(person);

        return !optional.isPresent();
    }
}

今、私たちは、ある人が現在の職に就いていないときに失業支援を受ける権利があることを確認するためのテストを作成したいとします。

その場合は、

findCurrentJobPosition()

に空の

Optional

を返させるようにします。

Mockito 2

以前は、このメソッドへの呼び出しをモックする必要がありました。

public class UnemploymentServiceImplUnitTest {

    @Mock
    private JobService jobService;

    @InjectMocks
    private UnemploymentServiceImpl unemploymentService;

    @Test
    public void givenReturnIsOfTypeOptional__whenMocked__thenValueIsEmpty() {
        Person person = new Person();

        when(jobService.findCurrentJobPosition(any(Person.class)))
          .thenReturn(Optional.empty());

        assertTrue(unemploymentService.personIsEntitledToUnemploymentSupport(person));
    }
}

13行目の

when(…​)。thenReturn(…​)

命令は、モックオブジェクトのモックオブジェクトへのメソッド呼び出しに対するデフォルトの戻り値が

null

であるため必要です。バージョン2ではその動作が変更されました。


Optionalを扱うときにnull値を扱うことはめったにないので、


Mockitoはデフォルトで空の

Optional

を返すようになりました

。これはhttps://docs.oracle.com/javase/8/docs/api/java/util/Optional.html#empty–[

Optional.empty()

]への呼び出しの戻りとまったく同じ値です。

したがって、

Mockitoバージョン2

を使用している場合は、13行目を削除してもテストは成功します。

public class UnemploymentServiceImplUnitTest {

    @Test
    public void givenReturnIsOptional__whenDefaultValueIsReturned__thenValueIsEmpty() {
        Person person = new Person();

        assertTrue(unemploymentService.personIsEntitledToUnemploymentSupport(person));
    }
}


3.2.

Stream


の例


Stream

を返すメソッドをモックすると、同じ動作が発生します。


JobService

インターフェースに、ある人が今まで働いたことのあるすべての職位を表すStreamを返す新しいメソッドを追加しましょう。

public interface JobService {
    Stream<JobPosition> listJobs(Person person);
}

このメソッドは、与えられた検索文字列に一致する仕事に人が取り組んだことがあるかどうかを問い合わせる別の新しいメソッドで使用されます。

public class UnemploymentServiceImpl implements UnemploymentService {

    @Override
    public Optional<JobPosition> searchJob(Person person, String searchString) {
        return jobService.listJobs(person)
          .filter((j) -> j.getTitle().contains(searchString))
          .findFirst();
    }
}

そのため、

listJobs()を書くことを心配せずに

searchJob()、__の実装を正しくテストしたいとし、その人がまだ仕事をしていないときにシナリオをテストしたいとします。

その場合、

listJobs()

が空の

Stream

を返すようにします。

  • Mockito 2より前では、そのようなテストを書くために

    listJobs()

    ** への呼び出しをモックする必要がありました。

public class UnemploymentServiceImplUnitTest {

    @Test
    public void givenReturnIsOfTypeStream__whenMocked__thenValueIsEmpty() {
        Person person = new Person();
        when(jobService.listJobs(any(Person.class))).thenReturn(Stream.empty());

        assertFalse(unemploymentService.searchJob(person, "").isPresent());
    }
}

  • バージョン2にアップグレードした場合、

    when(…​)。thenReturn(…​)

    呼び出しを削除することができます。これは、Mockitoがデフォルトでモックメソッドに対して空の

    Stream

    を返すためです。

public class UnemploymentServiceImplUnitTest {

    @Test
    public void givenReturnIsStream__whenDefaultValueIsReturned__thenValueIsEmpty() {
        Person person = new Person();

        assertFalse(unemploymentService.searchJob(person, "").isPresent());
    }
}


4ラムダ式を活用する

Java 8のラムダ式を使えば、ステートメントをずっとコンパクトにして読みやすくすることができます。 Mockitoを使用している場合、ラムダ式によってもたらされる単純さの2つの非常に良い例は


ArgumentMatchers


およびカスタム


回答



4.1. Lambdaと

ArgumentMatcher


の組み合わせ

Java 8より前では、

ArgumentMatcher

を実装したクラスを作成し、

matches()

メソッドにカスタムルールを記述する必要がありました。

Java 8では、内部クラスを単純なラムダ式に置き換えることができます。

public class ArgumentMatcherWithLambdaUnitTest {

    @Test
    public void whenPersonWithJob__thenIsNotEntitled() {
        Person peter = new Person("Peter");
        Person linda = new Person("Linda");

        JobPosition teacher = new JobPosition("Teacher");

        when(jobService.findCurrentJobPosition(
          ArgumentMatchers.argThat(p -> p.getName().equals("Peter"))))
          .thenReturn(Optional.of(teacher));

        assertTrue(unemploymentService.personIsEntitledToUnemploymentSupport(linda));
        assertFalse(unemploymentService.personIsEntitledToUnemploymentSupport(peter));
    }
}


4.2. LambdaとCustomの組み合わせ

回答


ラムダ式をMockitoの

Answer

と組み合わせると、同じ効果が得られます。

たとえば、

Person

の名前が“ Peter”の場合は単一の

JobPosition

を含む

Stream

を返し、それ以外の場合は空の

Stream

を返すように

listJobs()

メソッドの呼び出しをシミュレートする場合は、次のように作成する必要があります。

Answer

インタフェースを実装したクラス(匿名または内部)。

繰り返しになりますが、ラムダ式を使用すると、すべてのモック動作をインラインで記述できます。

public class CustomAnswerWithLambdaUnitTest {

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);

        when(jobService.listJobs(any(Person.class))).then((i) ->
          Stream.of(new JobPosition("Teacher"))
          .filter(p -> ((Person) i.getArgument(0)).getName().equals("Peter")));
    }
}

上記の実装では、

PersonAnswer

内部クラスは必要ないことに注意してください。


5結論

この記事では、Java 8とMockito 2の新機能を併用して、よりクリーンでシンプルで短いコードを書く方法について説明しました。ここで見たJava 8の機能のいくつかに慣れていないのであれば、いくつかの記事をチェックしてください。

  • リンク:/java-8-lambda-expressions-tips[ラムダ式と関数型]

インタフェース:ヒントとベストプラクティス]**

Java 8の新機能

また、https://github.com/eugenp/tutorials/tree/master/testing-modules/mockito-2[GitHubレポジトリ]で付随するコードを確認してください。