使用 @InjectMocks 注入 String 属性

Posted

技术标签:

【中文标题】使用 @InjectMocks 注入 String 属性【英文标题】:Injecting a String property with @InjectMocks 【发布时间】:2016-07-05 05:14:19 【问题描述】:

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

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

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

@RunWith(MockitoJUnitRunner.class)
public class AbcControllerTest 

    @Mock
    private XyzService mockXyzService;

    private String myProperty = "my property value";

    @InjectMocks
    private AbcController controllerUnderTest;

    /* tests */

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

@InjectMocks 在这种情况下默认注入一个空值。如果我把它放在myProperty 上,@Mock 会抛出异常,这是可以理解的。是否有另一个我错过的注释只是意味着“注入这个确切的对象而不是它的模拟”?

【问题讨论】:

我知道我可以在设置方法中手动实例化我的控制器,但我希望找到注释配置样式的解决方案。谢谢! 【参考方案1】:

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

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

【讨论】:

【参考方案2】:

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

【讨论】:

【参考方案3】:

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

【讨论】:

【参考方案4】:

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

做:

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

【讨论】:

【参考方案5】:

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

    重构“AbcController”类构造函数

    在您的测试类“之前”方法中,使用 Whitebox.setInternalState 方法指定您想要的任何字符串

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

【讨论】:

【参考方案6】:

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

@InjectMocks
MyClass myClass;

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

【讨论】:

或者你可以只使用setter方法。任何方式魔法都会消失,在这个阶段甚至在 @Before 方法中创建 MyClass 也是一种选择:(【参考方案7】:

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

这不是很简单,但至少您将能够拥有一个基于注释的纯测试,并带有一点存根。

【讨论】:

这很不幸。我查看了他们的问题工具,看起来他们已经discussed它before。一般的想法似乎是它超出了模拟框架的范围,甚至可能是代码味道,尽管我认为我不同意这一点。感谢您的帮助!

以上是关于使用 @InjectMocks 注入 String 属性的主要内容,如果未能解决你的问题,请参考以下文章

在使用@InjectMocks注入模拟对象内部之前初始化它

如何在spring boot中将@Value属性从application.properties注入@InjectMocks?

在 Mockito Junit 中 @InjectMocks 后为空

Kotlin,Spring book,Mockito,@InjectMocks,使用与创建的不同的模拟

如何在 Junit 中使用 @InjectMocks 和 @Autowired 注释

不使用InjectMocks创建对象会导致httpClient出现问题