Java 使用 @InjectMocks 注入 String 属性

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36084559/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 17:25:36  来源:igfitidea点击:

Injecting a String property with @InjectMocks

javaspringunit-testingdependency-injectionmockito

提问by Sam Jones

I have a Spring MVC @Controllerwith this constructor:

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

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

I want to write a standalone unit test for this Controller:

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

@RunWith(MockitoJUnitRunner.class)
public class AbcControllerTest {

    @Mock
    private XyzService mockXyzService;

    private String myProperty = "my property value";

    @InjectMocks
    private AbcController controllerUnderTest;

    /* tests */
}

Is there any way to get @InjectMocksto inject my String property? I know I can't mock a String since it's immutable, but can I just inject a normal String here?

有什么方法可以@InjectMocks注入我的 String 属性吗?我知道我不能模拟 String 因为它是不可变的,但是我可以在这里注入一个普通的 String 吗?

@InjectMocksinjects a null by default in this case. @Mockunderstandably throws an exception if I put it on myProperty. Is there another annotation I've missed that just means "inject this exact object rather than a Mock of it"?

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

采纳答案by Tom Verelst

You cannot do this with Mockito, because, as you mentioned yourself, a Stringis finaland cannot be mocked.

你不能用 Mockito 做到这一点,因为,正如你自己提到的, a Stringisfinal并且不能被嘲笑。

There is a @Spyannotation which works on realobjects, but it has the same limitations as @Mock, thus you cannot spy on a String.

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

There is no annotation to tell Mockito to just inject that value without doing any mocking or spying. It would be a good feature, though. Perhaps suggest it at the Mockito Github repository.

没有注释告诉 Mockito 只注入该值而不进行任何模拟或监视。不过,这将是一个很好的功能。也许在Mockito Github 存储库中建议它。

You will have to manually instantiate your controller if you don't want to change your code.

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

The only way to have a pure annotation based test is to refactor the controller. It can use a custom object that just contains that one property, or perhaps a configuration class with multiple properties.

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

@Component
public class MyProperty {

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

    ...
}

This can be injected into the controller.

这可以注入控制器。

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

You can mock and inject this then.

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

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

This is not pretty straight forward, but at least you will be able to have a pure annotation based test with a little bit of stubbing.

这不是很直接,但至少您将能够通过一点点存根进行纯基于注释的测试。

回答by Jacob Beasley

You can't do this with Mockito, but Apache Commons actually has a way to do this using one of its built in utilities. You can put this in a function in JUnit that is run after Mockito injects the rest of the mocks but before your test cases run, like this:

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

@InjectMocks
MyClass myClass;

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

回答by Abhinav Ganguly

What you can use is this : org.mockito.internal.util.reflection.Whitebox

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

  1. Refactor your "AbcController" class constructor
  2. In your Test class "before" method, use Whitebox.setInternalState method to specify whatever string you want

    @Before
    public void setUp(){
        Whitebox.setInternalState(controllerUnderTest, "myProperty", "The string that you want"); }
    
  1. 重构你的“AbcController”类构造函数
  2. 在您的测试类“before”方法中,使用 Whitebox.setInternalState 方法指定您想要的任何字符串

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

回答by Jan Groot

Just don't use @InjectMocks in that case.

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

do:

做:

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

回答by jebeaudet

Since you're using Spring, you can use the org.springframework.test.util.ReflectionTestUtilsfrom the spring-testmodule. It neatly wraps setting a field on a object or a static field on a class (along with other utility methods).

由于您使用的是 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 */
}

回答by Shayan Ahmad

Solution is simple: You should put constructor injection for the object type while for primitive/final dependencies you can simply use setter injection and that'll be fine for this scenario.

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

So this:

所以这:

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

Would be changed to:

将改为:

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

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

And the @Mockinjections in test would be as simple as:

@Mock测试中的注入将非常简单:

@Mock
private XyzService xyzService;

@InjectMocks
private AbcController abcController;

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

And that'll be enough. Going for Reflectionsis not advisable!

这就足够了。Reflections不建议去!

PLEASE AVOID THE USAGE OF REFLECTIONS IN PRODUCTION CODE AS MUCH AS POSSIBLE!!

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

For the solution of Jan GrootI must remind you that it will become very nasty since you will have to remove all the @Mockand even @InjectMocksand would have to initialize and then inject them manually which for 2 dependencies sound easy but for 7 dependencies the code becomes a nightmare (see below).

对于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");
}