フィールドインジェクションとコンストラクタインジェクションの違い

フィールドインジェクションとコンストラクタインジェクションは、Springフレームワークにおける依存性注入(DI)の方法です。ここでは、それぞれの違いやデメリット、そしてテストコードの具体例について説明します。

フィールドインジェクション

フィールドインジェクションは、@Autowiredアノテーションをフィールドに直接付けて、Springがそのフィールドに依存オブジェクトを注入する方法です。以下は、フィールドインジェクションの具体的な実装例です。


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class MyController {

    @Autowired
    private ItemService itemService;

    @GetMapping("/items")
    public String getItems(Model model) {
        model.addAttribute("items", itemService.getAllItems());
        return "itemList";
    }
}

@Service
public class ItemService {
    public List getAllItems() {
        return List.of("Item1", "Item2", "Item3");
    }
}
  • メリット: シンプルでコードが簡潔です。追加のコンストラクタやセッターを記述する必要がなく、クラスが短く保たれます。
  • デメリット:
    • 依存関係の非表示性: クラスの依存関係が明示されないため、コードの可読性が低下しやすく、依存関係が把握しづらい。
    • 不変性の欠如: フィールドをfinalにできず、一度設定された依存関係が変更される可能性があります。これは設計上の安全性を低下させます。
    • Springコンテナへの強い依存: クラスがSpringコンテナ内でしか動作しなくなり、単独での使用やテストが困難になる可能性があります。
    • 隠れた複雑性: 大規模プロジェクトや複雑なクラス設計では、フィールドインジェクションが設計を理解しにくくし、メンテナンスを難しくすることがあります。

コンストラクタインジェクション

コンストラクタインジェクションは、依存オブジェクトをクラスのコンストラクタを通じて注入する方法です。これにより、依存関係がクラスの外部から明示的に注入され、フィールドをfinalにすることができ、コードの安全性が向上します。


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class MyController {

    private final ItemService itemService;

    @Autowired
    public MyController(ItemService itemService) {
        this.itemService = itemService;
    }

    @GetMapping("/items")
    public String getItems(Model model) {
        model.addAttribute("items", itemService.getAllItems());
        return "itemList";
    }
}

@Service
public class ItemService {
    public List getAllItems() {
        return List.of("Item1", "Item2", "Item3");
    }
}
  • メリット:
    • 依存関係が明示され、クラスの設計が明確になります。
    • フィールドをfinalにでき、一度設定された依存オブジェクトが変更されないことが保証されます。
    • Springコンテナ以外でも、簡単に依存関係を提供できるため、テストやモジュール化が容易になります。
  • デメリット:
    • コードの冗長性: 依存関係が多い場合、コンストラクタが冗長になることがあり、コードが長くなります。
    • 大規模コンストラクタの複雑さ: コンストラクタに多くの依存オブジェクトが必要な場合、コードの可読性が低下することがあります。

テストコードにおける when-thenReturndoReturn-when の違い

テストコードを書く際に、when-thenReturndoReturn-whenは、Mockitoフレームワークでスタブを設定するための方法です。それぞれに適した場面があり、以下にその違いを説明します。

when-thenReturn

when-thenReturnは、通常のスタブを設定するために使用されます。モックされたオブジェクトの特定のメソッドが呼び出されたときに、指定した値を返すように設定します。通常、このメソッドはリターンタイプを持ち、呼び出し時に特定の戻り値を返すように定義されます。

when(mockService.someMethod()).thenReturn("expectedValue");

この場合、mockService.someMethod()が呼び出されると、常に"expectedValue"が返されます。

doReturn-when

doReturn-whenは、when-thenReturnが適用できない特殊なケースで使用されます。例えば、ターゲットメソッドがfinalであったり、例外が発生する場合、またはメソッド呼び出しによる副作用を避けたい場合に適しています。doReturn-whenを使用することで、メソッド呼び出しの副作用を発生させることなく、戻り値を設定することができます。

doReturn("expectedValue").when(mockService).someMethod();

この場合も、mockService.someMethod()が呼び出されると"expectedValue"が返されますが、when-thenReturnで発生する可能性のある副作用が回避されます。

各インジェクション時のテストコードの具体例

フィールドインジェクションの場合


@RunWith(MockitoJUnitRunner.class)
public class MyControllerTest {

    @InjectMocks
    private MyController myController;

    @Mock
    private ItemService itemService;

    @Test
    public void testGetItems_whenThenReturn() {
        when(itemService.getAllItems()).thenReturn(List.of("Item1", "Item2"));

        String view = myController.getItems(new ExtendedModelMap());

        assertEquals("itemList", view);
        verify(itemService).getAllItems();
    }

    @Test
    public void testGetItems_doReturnWhen() {
        doReturn(List.of("Item1", "Item2")).when(itemService).getAllItems();

        String view = myController.getItems(new ExtendedModelMap());

        assertEquals("itemList", view);
        verify(itemService).getAllItems();
    }
}

コンストラクタインジェクションの場合


public class MyControllerTest {

    @Test
    public void testGetItems_whenThenReturn() {
        ItemService mockService = mock(ItemService.class);
        when(mockService.getAllItems()).thenReturn(List.of("Item1", "Item2"));

        MyController myController = new MyController(mockService);

        String view = myController.getItems(new ExtendedModelMap());

        assertEquals("itemList", view);
        verify(mockService).getAllItems();
    }

    @Test
    public void testGetItems_doReturnWhen() {
        ItemService mockService = mock(ItemService.class);
        doReturn(List.of("Item1", "Item2")).when(mockService).getAllItems();

        MyController myController = new MyController(mockService);

        String view = myController.getItems(new ExtendedModelMap());

        assertEquals("itemList", view);
        verify(mockService).getAllItems();
    }
}

when-thenReturn と doReturn-when の使い分け

when-thenReturn: 通常のスタブを設定する際に使います。この方法は、対象メソッドが呼び出されたときに指定した値を返すようにします。対象メソッドが実際に呼び出されることを前提にしています。

doReturn-when: 例外やメソッドがfinalであるなど、特定の状況でwhen-thenReturnが適用できない場合に使用します。また、メソッドの呼び出しによる副作用を避けたい場合にも利用されます。doReturn-whenは、スタブ設定の前にメソッド呼び出しを避ける柔軟性を提供します。