【问题标题】:How to inject fields using Mockito without specifying them in constructor?如何使用 Mockito 注入字段而不在构造函数中指定它们?
【发布时间】:2019-06-10 19:37:15
【问题描述】:

我有一个使用 Mockito 测试的类 (PriceSetter),并且该类具有内部依赖项(数据库)。我想模拟这个内部依赖,然后将它注入到类中,但是我的构造函数中没有指定依赖。因此,Mockito 会自动尝试进行构造函数注入,并且永远不会注入依赖项。

我尝试在我的数据库对象上使用@Mock,在我的 PriceSetter 类上使用@InjectMocks,但 Mockito 自动调用构造函数,并且由于数据库未传递到构造函数中,它无法注入我的数据库模拟。

class PriceSetter {
    private Table priceTable;

    public PriceSetter(Dependency d1, Dependency d2) {
        this.d1 = d1;
        this.d2 = d2;
    }
}

@RunWith(MockitoJUnitRunner.class)
class PriceSetterTest{
    @InjectMocks
    private PriceSetter setter;

    @Mock Table priceTable;
    @Mock Dependency d1;
    @Mock Dependency d2;

    @Test
    public void someTestMethod() {
        when(priceTable.getItem(any())).thenReturn(Specified item);
        setter.priceTable.getItem("item"); -> Doesn't return item specified by mocked behavior
    }
}

我希望priceTable 被注入,但它没有被注入。只有 d1 和 d2 是通过构造函数注入来注入的。

【问题讨论】:

  • priceTable 是如何设置的?一个setter方法?
  • Injectmocks 实际上应该使用反射来设置所有字段。您确定这里的所有设置都正确吗?
  • 这取决于 priceTable 在真实类中的设置方式。

标签: java dependency-injection mockito


【解决方案1】:

@InjectMocks 只会执行构造函数注入或属性注入之一,而不是两者。

Mockito 将尝试通过构造函数注入注入模拟, setter 注入,或者按顺序注入属性...

你总是可以的

@Before
public void setUp() {
   setter.setPriceTable(priceTable);
}

或者你的桌子应该连线。但是,最简洁的设计通常是统一您的依赖注入方法,将所有内容注入到构造函数中。由于@InjectMocks 将选择最大的构造函数并使用私有或包私有构造函数,因此一种选择是添加构造函数重载:

class PriceSetter {
    private Table priceTable;

    public PriceSetter(Dependency d1, Dependency d2) {
        this(d1, d2, new DefaultPriceTable());
    }

    PriceSetter(Dependency d1, Dependency d2, Table priceTable) {
        this.d1 = d1;
        this.d2 = d2;
        this.priceTable = priceTable;
    }

}

【讨论】:

  • 谢谢,重载构造函数来注入依赖是一种干净的方法。
【解决方案2】:

这样的测试通过了:

 @RunWith(MockitoJUnitRunner.class)
public class PriceSetterTest{

    public PriceSetterTest() {}

    @InjectMocks
    private PriceSetter priceSetter;

    @Mock
    Table priceTable;

    @Mock
    Dependency1 d1;

    @Mock Dependency1 d2;

    @Test
    public void someTestMethod() {
        when(priceTable.getItem(any())).thenReturn("aa" );
        Assert.assertEquals("aa",priceTable.getItem("item"));
    }
}

你应该添加一个默认构造函数(不带参数)和 做priceTable.getItem("item")而不是setter.priceTable.getItem("item");

【讨论】:

    【解决方案3】:

    如果您使用旧的 Mockito 版本,那么快速但肮脏的方法是使用 org.mockito.internal.util.reflection.Whitebox.setInternalState 修改测试对象的内部状态(它使用反射在构造函数调用后修改字段)。

    @Test
    public void someTestMethod() {
        when(priceTable.getItem(any())).thenReturn("MOCKED!");
        Whitebox.setInternalState(setter, "priceTable", priceTable);
        Assert.assertEquals("MOCKED!",  setter.priceTable.getItem("item"));
    }
    

    这种方法的缺点是“内部”Mockito 包和功能隐藏在较新版本中。顺便说一句,有several workarounds在这种情况下如何处理;)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-15
      • 1970-01-01
      相关资源
      最近更新 更多