【问题标题】:how to force spring boot to set autowired fields in unit tests如何强制spring boot在单元测试中设置自动装配字段
【发布时间】:2019-09-08 10:52:30
【问题描述】:

我正在开发一个 Spring Boot 应用程序。我试图开发一些单元测试。 单元测试必须在没有在容器中启动应用程序的情况下运行,必须非常快以至于可以在没有很多时间的情况下运行。 想象一下我有一个类名ApplicationService,如下:

@Service
public class ApplicationService {

     @Autowired
     private ApplicationRepository applicationRepository;


     @Autowired
     private final PasswordEncoder passwordEncoder;

     ///............
}

及以下测试:

@RunWith(SpringRunner.class)
@TestExecutionListeners(MockitoTestExecutionListener.class)
@ContextConfiguration
public class CreateApplicationTest {

    @Autowired
    @InjectMocks
    ApplicationService applicationService;

    @Mock
    private ApplicationRepository applicationRepository;

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

    @Test
    public void createApplication() {
        CreateApplicationDTO dto = new CreateApplicationDTO("some info");
        given(this.applicationService.createApplication(dto)).willReturn(null);
        ApplicationResultDTO application = applicationService.createApplication(dto);
        assertThat(application, is(someValue));
    }
}

虽然模拟是正确且有效的(在此示例中 ApplicationService.applicationRepository 是模拟的),但其他 AutoWired 字段(在此示例中 ApplicationService.passwordEncoder)仍然为空。

如何配置测试以将默认实例(来自 bean)应用到其他字段?

【问题讨论】:

    标签: spring-boot unit-testing mockito javabeans autowired


    【解决方案1】:

    PasswordEncoder 添加到注释@Spy @Autowired 的测试类中,以便它可以在模拟ApplicationService 中使用

    @Spy
    @Autowired
    private  PasswordEncoder passwordEncoder;
    

    【讨论】:

    • 这不起作用并产生错误“Mockito 无法模拟此类:接口 .....ApplicationRepository。Mockito 只能模拟非私有和非最终类。”在依赖项中成为“mockito-core”会导致此错误,即使不使用@Spy
    • @HomayounBehzadian 你应该 Spy 你使用的相关 PasswordEncoder 实现
    • 你是什么意思?在PasswordEncoder 的类上应用@Spy?或PasswordEncoder类型的字段?
    • @HomayounBehzadian 监视测试类内的字段
    • 在测试类中的字段上使用@Spy 等同于使用@Mock。我刚刚测试了两个字段都设置为模拟对象。我需要将第二个字段设置为实际值
    【解决方案2】:

    对于单元测试,您不需要SpringRunner(或SpringExtension)。它们更适合集成测试。他们也很慢。单元测试的目的是测试隔离类。因此,Mockito 更适合这项任务。

    通常,我会以不同的方式实现它。我建议在构造函数上使用 @Inject 而不是 @Autowired 字段,如下所示:

    @Service
    public class ApplicationService {
    
         private ApplicationRepository applicationRepository;
    
         private final PasswordEncoder passwordEncoder;
    
         @Inject
         public ApplicationService(ApplicationRepository applicationRepository, PasswordEncoder passwordEncoder) {
             this.applicationRepository = applicationRepository;
             this.passwordEncoder = passwordEncoder;
         }
    
         ///............
    }
    

    现在你有了一个可以使用的带有构造函数的类。你不需要任何依赖注入。

    测试类可以是这样的:

    @ExtendWith(MockitoExtension.class) //junit5, but junit4 is also possible
    public class ApplicationServiceTest {
    
         @Mock
         private ApplicationRepository applicationRepository;
    
         @Mock
         private final PasswordEncoder passwordEncoder;
    
         @InjectMocks
         private ApplicationService sut; //ServiceUnderTest
    
         // Now write your tests
    }
    

    【讨论】:

    • 我不想模拟所有的依赖,因为只有其中一些用于特定的测试,而且还有很多依赖(超过 30 个),其中一些不能模拟,每个依赖可能有内部服务
    • 特别是因为您需要模拟依赖项。您只想测试 1 类!不是依赖类。模拟每个依赖项,您可以控制所有功能,而无需控制子依赖项。
    【解决方案3】:

    您可以使用 doReturn().when(); 简单地从测试用例本身返回 passwordEncoder 的值。

    @RunWith(SpringRunner.class)
    @TestExecutionListeners(MockitoTestExecutionListener.class)
    @ContextConfiguration
    public class CreateApplicationTest {
    
    @Autowired
    @InjectMocks
    ApplicationService applicationService;
    
    @Mock
    private ApplicationRepository applicationRepository;
    
    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }
    
    @Test
    public void createApplication() {
    
        `doReturn("value of passwordEncoder").
             when(applicationService).getPasswordEncoder();`
    
        CreateApplicationDTO dto = new CreateApplicationDTO("some info");
        given(this.applicationService.createApplication(dto)).willReturn(null);
        ApplicationResultDTO application = applicationService.createApplication(dto);
        assertThat(application, is(someValue));
    }
    

    }

    【讨论】:

      猜你喜欢
      • 2016-03-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-20
      • 2016-09-23
      • 2021-12-23
      • 2015-09-11
      • 2019-04-10
      相关资源
      最近更新 更多