使用@InjectMocks

时间:2016-03-18 12:35:26

标签: java spring unit-testing dependency-injection mockito

我有一个带有这个构造函数的Spring MVC @Controller

@Autowired
public AbcController(XyzService xyzService, @Value("${my.property}") String myProperty) {/*...*/}

我想为此Controller编写一个独立的单元测试:

@RunWith(MockitoJUnitRunner.class)
public class AbcControllerTest {

    @Mock
    private XyzService mockXyzService;

    private String myProperty = "my property value";

    @InjectMocks
    private AbcController controllerUnderTest;

    /* tests */
}

有没有办法让@InjectMocks注入我的String属性?我知道我不能模拟一个字符串,因为它是不可变的,但我可以在这里注入一个普通的字符串吗?

在这种情况下,

@InjectMocks默认注入null。如果我把@Mock放在myProperty上,Miter会引发异常。是否有另外一个我错过的注释,只是意味着“注入这个确切的对象而不是它的模拟”?

7 个答案:

答案 0 :(得分:21)

您无法使用Mockito执行此操作,但Apache Commons实际上可以使用其内置实用程序之一来实现此目的。你可以将它放在JUnit中的一个函数中,该函数在Mockito注入其余的模拟之后但在测试用例运行之前运行,如下所示:

@InjectMocks
MyClass myClass;

@Before
public void before() throws Exception {
    FieldUtils.writeField(myClass, "fieldName", fieldValue, true);
}

答案 1 :(得分:8)

你不能用Mockito这样做,因为正如你自己提到的那样,Stringfinal并且不能被嘲笑。

@Spy注释适用于真实的对象,但它与@Mock具有相同的限制,因此您无法监视String

没有任何注释可以告诉Mockito在不进行任何嘲弄或间谍活动的情况下注入该值。不过,这将是一个很好的功能。也许在Mockito Github repository建议。

如果您不想更改代码,则必须手动实例化控制器。

进行基于纯注释的测试的唯一方法是重构控制器。它可以使用仅包含该属性的自定义对象,也可以使用具有多个属性的配置类。

@Component
public class MyProperty {

    @Value("${my.property}")
    private String myProperty;

    ...
}

这可以注入控制器。

@Autowired
public AbcController(XyzService xyzService, MyProperty myProperty) { 
    ... 
}

然后你可以模拟并注入它。

@RunWith(MockitoJUnitRunner.class)
public class AbcControllerTest {

    @Mock
    private XyzService mockXyzService;

    @Mock
    private MyProperty myProperty;

    @InjectMocks
    private AbcController controllerUnderTest;

    @Before
    public void setUp(){
        when(myProperty.get()).thenReturn("my property value");
    }

    /* tests */
}

这不是很直截了当,但至少你可以通过一些简单的方法进行纯粹的基于注释的测试。

答案 2 :(得分:1)

在这种情况下,请勿使用@InjectMocks。

要做:

@Before
public void setup() {
 controllerUnderTest = new AbcController(mockXyzService, "my property value");
}

答案 3 :(得分:0)

您可以使用的是: org.mockito.internal.util.reflection.Whitebox

  1. 重构“ AbcController”类的构造函数
  2. 在您的Test类“之前”方法中,使用Whitebox.setInternalState方法指定所需的任何字符串

    @Before
    public void setUp(){
        Whitebox.setInternalState(controllerUnderTest, "myProperty", "The string that you want"); }
    

答案 4 :(得分:0)

由于使用的是Spring,因此可以使用org.springframework.test.util.ReflectionTestUtils模块中的spring-test。整齐地包装在对象上设置字段或在类上设置静态字段(以及其他实用程序方法)。

@RunWith(MockitoJUnitRunner.class)
public class AbcControllerTest {

    @Mock
    private XyzService mockXyzService;

    @InjectMocks
    private AbcController controllerUnderTest;

    @Before
    public void setUp(){
        ReflectionTestUtils.setField(controllerUnderTest, "myProperty", 
               "String you want to inject");
    }

    /* tests */
}

答案 5 :(得分:0)

解决方案很简单:您应该为对象类型放置构造函数注入,而对于原始/最终依赖项,您可以简单地使用setter注入,在这种情况下就可以了。

所以这个:

public AbcController(XyzService xyzService, @Value("${my.property}") String myProperty) {/*...*/}

将更改为:

@Autowired
public AbcController(XyzService xyzService) {/*...*/}

@Autowired
public setMyProperty(@Value("${my.property}") String myProperty){/*...*/}

测试中的@Mock注入很简单:

@Mock
private XyzService xyzService;

@InjectMocks
private AbcController abcController;

@BeforeMethod
public void setup(){
    MockitoAnnotations.initMocks(this);
    abcController.setMyProperty("new property");
}

就足够了。建议不要使用Reflections

请尽可能避免在生产代码中使用反射!

对于Jan Groot的解决方案,我必须提醒您,它将变得非常讨厌,因为您将必须删除所有的@Mock甚至是@InjectMocks,并且必须进行初始化然后注入手动操作它们对于2个依赖关系听起来很容易,但是对于7个依赖关系,代码就成了噩梦(见下文)。

private XyzService xyzService;
private AbcController abcController;

@BeforeMethod
public void setup(){ // NIGHTMARE WHEN MORE DEPENDENCIES ARE MOCKED!
    xyzService = Mockito.mock(XyzService.class);
    abcController = new AbcController(xyzService, "new property");
}

答案 6 :(得分:0)

如果您不想在代码中进行任何更改,请使用ReflectionTestUtils.setField方法 How do I mock an autowired @Value field in Spring with Mockito?