我有一个带有这个构造函数的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
会引发异常。是否有另外一个我错过的注释,只是意味着“注入这个确切的对象而不是它的模拟”?
答案 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这样做,因为正如你自己提到的那样,String
是final
并且不能被嘲笑。
有@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
在您的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?