spring 使用@InjectMocks注入String属性

hgncfbus  于 2023-04-19  发布在  Spring
关注(0)|答案(8)|浏览(217)

我有一个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属性吗?我知道我不能模拟一个String,因为它是不可变的,但是我可以在这里注入一个普通的String吗?
在这种情况下,@InjectMocks默认注入一个null。如果我把它放在myProperty上,@Mock可以理解地抛出一个异常。是否还有另一个注解我错过了,它只是意味着“注入这个确切的对象,而不是它的一个Mock”?

xdnvmnnf

xdnvmnnf1#

Mockito不能做到这一点,但Apache Commons实际上有一种方法可以使用其内置的实用程序来做到这一点。您可以将其放入JUnit中的一个函数中,该函数在Mockito注入其余的mocks之后运行,但在测试用例运行之前运行,如下所示:

@InjectMocks
MyClass myClass;

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

xmakbtuz2#

由于您使用的是Spring,因此可以使用spring-test模块中的org.springframework.test.util.ReflectionTestUtils。它巧妙地 Package 了在对象上设置字段或在类上设置静态字段(沿着其他实用程序方法)。

@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 */
}
af7jpaap

af7jpaap3#

你不能在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 */
}

这并不是很直接,但至少你可以有一个纯粹的基于注解的测试,只需要一点存根。

hkmswyz6

hkmswyz64#

在这种情况下不要使用@InjectMocks。
做到:

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

juzqafwq5#

解决方法简单:你应该为对象类型注入构造函数,而对于原始/最终依赖项,你可以简单地使用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");
}
mm9b1k5b

mm9b1k5b6#

你可以使用的是:org.mockito.internal.util.reflection.Whitebox
1.重构“AbcController”类构造函数
1.在Test类的“before”方法中,使用Whitebox.setInternalState方法指定所需的任何字符串

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

k7fdbhmy7#

如果您不想更改代码,那么使用ReflectionTestUtils.setField方法How do I mock an autowired @Value field in Spring with Mockito?

isr3a4wc

isr3a4wc8#

TL;DR

@Autowired
@InjectMocks
private AbcController controllerUnderTest;

解释:你会惊讶地发现它是多么简单,但却很棘手。尝试用途:@Autowired和**@InjectMock**注解在一起。

通过这种方式,你的实际对象将首先被加载,然后需要被监视或模仿的对象将被注入。你的实际对象将具有已经通过属性填充的值。

相关问题