【问题标题】:Injecting a String property with @InjectMocks使用 @InjectMocks 注入 String 属性
【发布时间】: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 会抛出异常,这是可以理解的。是否有另一个我错过的注释只是意味着“注入这个确切的对象而不是它的模拟”?

【问题讨论】:

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

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


【解决方案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

          1. 重构“AbcController”类构造函数
          2. 在您的测试类“之前”方法中,使用 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 */
            }
            

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

            【讨论】:

            • 这很不幸。我查看了他们的问题工具,看起来他们已经discussedbefore。一般的想法似乎是它超出了模拟框架的范围,甚至可能是代码味道,尽管我认为我不同意这一点。感谢您的帮助!
            猜你喜欢
            • 2019-02-10
            • 2021-11-24
            • 1970-01-01
            • 2014-11-02
            • 2012-04-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多