3

例外処理をテストするのに役立ちます。この特定のケースでは、特定のクラスの非整列化中に例外がスローされたときに特定のタスクを実行するエクストラクタがあります。

サンプルコード

以下はコードの簡単な例です。製品版はもっと複雑です。

public class Example {
    public static enum EntryType {
        TYPE_1,
        TYPE_2
    }

    public static class Thing {
        List<String> data = new ArrayList<String>();
        EnumSet<EntryType> failedConversions = EnumSet.noneOf(EntryType.class);
    }

    public static class MyHelper {
        public String unmarshal(String input) throws UnmarshalException {
            // pretend this does more complicated stuff
            return input + " foo "; 
        }
    }

    public static class MyService {

        MyHelper adapter = new MyHelper();

        public Thing process() {
            Thing processed = new Thing();

            try {
                adapter.unmarshal("Type 1");
            } catch (UnmarshalException e) {
                processed.failedConversions.add(EntryType.TYPE_1);
            }

            // do some stuff

            try {
                adapter.unmarshal("Type 2");
            } catch (UnmarshalException e) {
                processed.failedConversions.add(EntryType.TYPE_2);
            }

            return processed;
        }
    }
}

私が試したこと

これが私が試したことのリストです。簡潔にするために、私はすべてのありふれた詳細を埋めていません。

スパイ

次のメソッドは何も実行せず、例外はスローされません。理由はわかりません。

@Test
public void shouldFlagFailedConversionUsingSpy()
        throws Exception {
    MyHelper spied = spy(fixture.adapter);
    doThrow(new UnmarshalException("foo")).when(spied).unmarshal(
            Mockito.eq("Type 1"));

    Thing actual = fixture.process();
    assertEquals(1, actual.failedConversions.size());
    assertThat(actual.failedConversions.contains(EntryType.TYPE_1), is(true));
}

あざける

例外をスローするメソッドでは部分モックがうまく機能しないように見えるため、以下は機能しませんでした。

@Test
public void shouldFlagFailedConversionUsingMocks()
        throws Exception {
    MyHelper mockAdapter = mock(MyHelper.class);
    when(mockAdapter.unmarshal(Mockito.anyString())).thenCallRealMethod();
    when(mockAdapter.unmarshal(Mockito.eq("Type 2"))).thenThrow(
            new UnmarshalException("foo"));

    Thing actual = fixture.process();
    assertEquals(1, actual.failedConversions.size());
    assertThat(actual.failedConversions.contains(EntryType.TYPE_2), is(true));
}

次に答える

これは機能しますが、これが適切な方法であるかどうかはわかりません。

@Test
public void shouldFlagFailedConversionUsingThenAnswer() throws Exception {
    final MyHelper realAdapter = new MyHelper();
    MyHelper mockAdapter = mock(MyHelper.class);
    fixture.adapter = mockAdapter;

    when(mockAdapter.unmarshal(Mockito.anyString())).then(
            new Answer<String>() {

                @Override
                public String answer(InvocationOnMock invocation)
                        throws Throwable {
                    Object[] args = invocation.getArguments();
                    String input = (String) args[0];
                    if (input.equals("Type 1")) {
                        throw new UnmarshalException("foo");
                    }
                    return realAdapter.unmarshal(input);
                }

            });

    Thing actual = fixture.process();
    assertEquals(1, actual.failedConversions.size());
    assertThat(actual.failedConversions.contains(EntryType.TYPE_1), is(true));
}

質問

このthenAnswer方法は機能しますが、適切な解決策ではないようです。この状況で部分モックを実行する正しい方法は何ですか?

4

1 に答える 1

1

あざけりやスパイ行為で何をしていたのかよくわかりませんが、ここで本当に必要なのはあざけることだけです。

まず、何らかの理由であなたのモックを試しているときに、いくつかの障害に遭遇しました。spyこれは、何らかの形でめちゃくちゃになった電話に関係していると思います。私は最終的にこれらを克服しましたが、簡単なものを渡したかったのです。

次に、あなたがスパイしていた方法に何か問題があることに気付きました (私のアプローチの基礎):

MyHelper spied = spy(fixture.adapter);

これは、スパイされたのではなくMyHelper、モックアウトされたインスタンスが必要であることを意味します。最悪の部分は、このオブジェクトが完全に水和されていたとしても、テスト オブジェクトに再割り当てしていないため、適切に注入されないことです (これは だと思います)。fixture

私の好みは、MockitoJUnitRunnerモック化されたインスタンスの注入を支援するために を使用することです。そこから、実際にモックする必要があるものの基礎を構築します。

モック化されたインスタンスが 1 つしかなく、次にテスト オブジェクトがあり、この宣言により、インスタンス化と注入の両方が確実に行われます。

@RunWith(MockitoJUnitRunner.class)
public class ExampleTest {

    @Mock
    private MyHelper adapter;

    @InjectMocks
    private MyService fixture;
}

アイデアは、モックフィクスチャに注入しているということです。これを使用する必要はありません。@Before宣言で標準のセッターを使用することもできますが、モックを機能させるために記述しなければならないボイラープレート コードが大幅に削減されるため、私はこれを好みます。

ここで行う変更は 1 つだけです。spy インスタンスを削除し、以前の使用法を実際のモックに置き換えます。

doThrow(new UnmarshalException("foo")).when(adapter).unmarshal(eq("Type 1"));

すべてのコードを巻き上げると、次のようになります。

@RunWith(MockitoJUnitRunner.class)
public class ExampleTest {
    @Mock
    private MyHelper adapter;

    @InjectMocks
    private MyService fixture;

    @Test
    public void shouldFlagFailedConversionUsingSpy()
            throws Exception {

        doThrow(new UnmarshalException("foo")).when(adapter).unmarshal(eq("Type 1"));

        Thing actual = fixture.process();
        assertEquals(1, actual.failedConversions.size());
        assertThat(actual.failedConversions.contains(Example.EntryType.TYPE_1), is(true));
    }
}

質問/ユースケースを不完全のままにしたくないので、一周してテストを内部クラスに置き換えましたが、それも正常に機能します:

@RunWith(MockitoJUnitRunner.class)
public class ExampleTest {
    @Mock
    private Example.MyHelper adapter;

    @InjectMocks
    private Example.MyService fixture;

    @Test
    public void shouldFlagFailedConversionUsingSpy()
            throws Exception {

        doThrow(new UnmarshalException("foo")).when(adapter).unmarshal(eq("Type 1"));

        Example.Thing actual = fixture.process();
        assertEquals(1, actual.failedConversions.size());
        assertThat(actual.failedConversions.contains(Example.EntryType.TYPE_1), is(true));
    }
}
于 2015-05-02T05:25:17.230 に答える