【问题标题】:Spring @Autowired constructor causes @Value to return null when instantiated in test classSpring @Autowired 构造函数导致 @Value 在测试类中实例化时返回 null
【发布时间】:2016-10-24 19:34:48
【问题描述】:

我在服务中使用自动装配的构造函数,当在测试类中实例化该构造函数时,会导致 @Value 注释返回 null。自动装配依赖关系直接解决了这个问题,但项目遵循使用基于构造函数的自动装配的约定。我的理解是,在测试类中实例化服务并不是从导致 @Value 返回 null 的 Spring IoC 容器创建它。有没有一种方法可以使用基于构造函数的自动装配从 IoC 容器创建服务,而无需直接访问应用程序上下文?

示例服务:

@Component
public class UpdateService {

   @Value("${update.success.table}")
   private String successTable;

   @Value("${update.failed.table}")
   private String failedTable;

   private UserService userService

   @Autowired
   public UpdateService(UserService userService) {
      this.userService = userService;
   }
}

示例测试服务:

@RunWith(SpringJUnite4ClassRunner.class)
@SpringApplicationConfiguration(classes = {TestApplication.class})
@WebAppConfiguration
public class UpdateServiceTest {

   private UpdateService updateService;

   @Mock
   private UserService mockUserService;

   @Before
   public void setUp() {
      MockitoAnnotations.initMocks(this);

      updateService = new UpdateService(mockUserService);

   }
}

【问题讨论】:

  • 如果你有一个 UpdateService bean,只需 @Autowired 它。
  • 不幸的是,这对我没有帮助,因为我需要能够在测试类中使用模拟对象构造 UpdateService。
  • 对,错过了。查看here 以获取提示。

标签: java spring mockito autowired spring-test


【解决方案1】:

要使 @Value 工作 updateService 应该在 spring 上下文中。

spring 框架集成测试的最佳实践是在测试上下文中包含应用程序上下文并在测试中自动装配测试源:

...
public class UpdateServiceTest  { 
    @Autowired 
    private UpdateService updateService;
    ...

模拟用户服务

userService 更改为protected 并考虑测试和源类在同一个包中的选项。

@Before
public void setUp() {
   MockitoAnnotations.initMocks(this);

   updateService.userService = mockUserService;
}

带有Whitebox的反射选项:

@Before
public void setUp() {
   MockitoAnnotations.initMocks(this);

   Whitebox.setInternalState(updateService, 'userService', mockUserService);
}

【讨论】:

  • 不幸的是,直接自动写入 UpdateService 对我没有帮助,因为我需要能够使用模拟的 UserService 对象构造对象
  • @Alex 没关系,因为你有两个选择,你可以让private UserService userService 受保护,并通过分配模拟实例或使用反射来更改私有字段来将真实 bean 与模拟交换。
  • 我想避免直接设置 UserService。不过,在 Whitebox 中使用反射可以解决问题,谢谢!
  • 我意识到只有在测试类的 updateService 上使用自动装配注释时,使用 Whitebox 进行反射才有效。尝试使用构造函数构建服务然后调用 .setInternalState() 时,它不起作用。我想坚持解决方案,但我的老板不满意:/
  • 如果你不自动装配 updateService 它将脱离 Spring 的上下文并且你不能在它的上下文之外使用 Spring Annotations。所以你没有太多选择。
【解决方案2】:

@Value 由属性占位符配置器填充,该配置器是 spring 上下文中的后处理器。由于您的 UpdateService 不是上下文的一部分,因此不会对其进行处理。

您的设置看起来有点像单元测试和集成测试的不明确混合。对于单元测试,您根本不需要 spring 上下文。只需将@Value 带注释的成员包保护并设置它们或使用ReflectionTestUtils.setField()(均显示):

public class UpdateServiceTest {

   @InjectMocks
   private UpdateService updateService;

   @Mock
   private UserService mockUserService;

   @Before
   public void setUp() {
      MockitoAnnotations.initMocks(this);
      ReflectionTestUtils.setField(updateService, "successTable", "my_success");
      updateService.failedTable = "my_failures";
   }
}

对于集成测试,所有接线都应在 spring 完成。

为此,我添加了一个提供模拟用户服务的内部配置类(@Primary 仅适用于您的上下文中有任何其他用户服务的情况)并且模拟存储在此处的静态成员中以进行简单访问之后从测试中模拟出来。

@RunWith(SpringJUnite4ClassRunner.class)
@SpringApplicationConfiguration(classes = {TestApplication.class, UpdateServiceTest.TestAddOn.class})
@WebAppConfiguration
public class UpdateServiceTest {

   @Autowired
   private UpdateService updateService;

   private static UserService mockUserService;



   static class TestAddOn {
      @Bean
      @Primary
      UserService updateService() {
        mockUserService = Mockito.mock(UserService.class);
        return mockUserService;
      }
   }
}

【讨论】:

    猜你喜欢
    • 2012-12-03
    • 1970-01-01
    • 1970-01-01
    • 2015-09-28
    • 2016-09-16
    • 2019-03-27
    • 1970-01-01
    • 1970-01-01
    • 2020-04-12
    相关资源
    最近更新 更多