web-dev-qa-db-ja.com

モッキートでシングルトンをモックする

メソッド呼び出しでシングルトンを使用するレガシーコードをテストする必要があります。テストの目的は、clas sunderテストがsingletonsメソッドを確実に呼び出すことです。私はSOで同様の質問を見ましたが、すべての答えには他の依存関係(異なるテストフレームワーク)が必要です-残念ながらMockitoとJUnitの使用に制限されていますが、これはそのような人気のあるフレームワークで完全に可能です。

シングルトン:

public class FormatterService {

    private static FormatterService INSTANCE;

    private FormatterService() {
    }

    public static FormatterService getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new FormatterService();
        }
        return INSTANCE;
    }

    public String formatTachoIcon() {
        return "URL";
    }

}

テスト対象のクラス:

public class DriverSnapshotHandler {

    public String getImageURL() {
        return FormatterService.getInstance().formatTachoIcon();
    }

}

単体テスト:

public class TestDriverSnapshotHandler {

    private FormatterService formatter;

    @Before
    public void setUp() {

        formatter = mock(FormatterService.class);

        when(FormatterService.getInstance()).thenReturn(formatter);

        when(formatter.formatTachoIcon()).thenReturn("MockedURL");

    }

    @Test
    public void testFormatterServiceIsCalled() {

        DriverSnapshotHandler handler = new DriverSnapshotHandler();
        handler.getImageURL();

        verify(formatter, atLeastOnce()).formatTachoIcon();

    }

}

テスト対象のクラスはgetInstanceを呼び出してからformatTachoIconメソッドを呼び出すため、この考えは恐ろしいシングルトンの予想される動作を構成することでした。残念ながら、これはエラーメッセージで失敗します。

when() requires an argument which has to be 'a method call on a mock'.
25
fbielejec

レガシーコードは静的メソッドgetInstance()に依存しており、Mockitoは静的メソッドのモックを許可していないため、次の行は機能しません。

when(FormatterService.getInstance()).thenReturn(formatter);

この問題を回避するには2つの方法があります。

  1. 静的メソッドをモックできるPowerMockなどの別のモッキングツールを使用します。

  2. コードをリファクタリングして、静的メソッドに依存しないようにします。これを達成するために考えられる最も侵襲的な方法は、DriverSnapshotHandler依存関係を注入するFormatterServiceにコンストラクターを追加することです。このコンストラクターはテストでのみ使用され、実稼働コードは引き続き実際のシングルトンインスタンスを使用します。

    public static class DriverSnapshotHandler {
    
        private final FormatterService formatter;
    
        //used in production code
        public DriverSnapshotHandler() {
            this(FormatterService.getInstance());
        }
    
        //used for tests
        DriverSnapshotHandler(FormatterService formatter) {
            this.formatter = formatter;
        }
    
        public String getImageURL() {
            return formatter.formatTachoIcon();
        }
    }
    

次に、テストは次のようになります。

FormatterService formatter = mock(FormatterService.class);
when(formatter.formatTachoIcon()).thenReturn("MockedURL");
DriverSnapshotHandler handler = new DriverSnapshotHandler(formatter);
handler.getImageURL();
verify(formatter, atLeastOnce()).formatTachoIcon();
27
noscreenname

可能だと思います。例を参照してください シングルトンのテスト方法

テスト前:

@Before
public void setUp() {
    formatter = mock(FormatterService.class);
    setMock(formatter);
    when(formatter.formatTachoIcon()).thenReturn(MOCKED_URL);
}

private void setMock(FormatterService mock) {
    try {
        Field instance = FormatterService.class.getDeclaredField("instance");
        instance.setAccessible(true);
        instance.set(instance, mock);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

テスト後-クラスをクリーンアップすることが重要です。他のテストは、モックされたインスタンスと混同されるためです。

@After
public void resetSingleton() throws Exception {
   Field instance = FormatterService.class.getDeclaredField("instance");
   instance.setAccessible(true);
   instance.set(null, null);
}

テスト:

@Test
public void testFormatterServiceIsCalled() {
    DriverSnapshotHandler handler = new DriverSnapshotHandler();
    String url = handler.getImageURL();

    verify(formatter, atLeastOnce()).formatTachoIcon();
    assertEquals(MOCKED_URL, url);
}
13
Kyrylo Semenko

GetInstanceメソッドは静的であるため、mockitoを使用してモックすることはできません。 http://cube-drone.com/media/optimized/172.pngPowerMockito を使用することもできます。この方法で行うことはお勧めしませんが。依存性注入を介してDriverSnapshotHandlerをテストします。

public class DriverSnapshotHandler {

    private FormatterService formatterService;

    public DriverSnapshotHandler(FormatterService formatterService) {
        this.formatterService = formatterService;
    }

    public String getImageURL() {
        return formatterService.formatTachoIcon();
    }

}

単体テスト:

public class TestDriverSnapshotHandler {

    private FormatterService formatter;

    @Before
    public void setUp() {

        formatter = mock(FormatterService.class);

        when(formatter.formatTachoIcon()).thenReturn("MockedURL");

    }

    @Test
    public void testFormatterServiceIsCalled() {

        DriverSnapshotHandler handler = new DriverSnapshotHandler(formatter);
        handler.getImageURL();

        verify(formatter, times(1)).formatTachoIcon();

    }

}

@Afterメソッドでモックをnullに設定できます。これが私見のクリーナーソリューションです。

1