为什么组件扫描不适用于 Spring Boot 单元测试?

Nil*_*esh 5 java spring integration-testing unit-testing spring-boot

服务类用@Service又名FooServiceImpl注释,这使其符合自动装配的条件。为什么在单元测试期间这个类没有被拾取和自动装配?@Component

@Service
public class FooServiceImpl implements FooService {
    @Override
    public String reverse(String bar) {
        return new StringBuilder(bar).reverse().toString();
    }
}

@RunWith(SpringRunner.class)
//@SpringBootTest
public class FooServiceTest {
    @Autowired
    private FooService fooService;
    @Test
    public void reverseStringShouldReverseAnyString() {
        String reverse = fooService.reverse("hello");
        assertThat(reverse).isEqualTo("olleh");
    }
}
Run Code Online (Sandbox Code Playgroud)

测试未能加载应用程序上下文,

2018-02-08T10:58:42,385 INFO    Neither @ContextConfiguration nor @ContextHierarchy found for test class [io.github.thenilesh.service.impl.FooServiceTest], using DelegatingSmartContextLoader
2018-02-08T10:58:42,393 INFO    Could not detect default resource locations for test class [io.github.thenilesh.service.impl.FooServiceTest]: no resource found for suffixes {-context.xml}.
2018-02-08T10:58:42,394 INFO    Could not detect default configuration classes for test class [io.github.thenilesh.service.impl.FooServiceTest]: FooServiceTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2018-02-08T10:58:42,432 INFO    Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, (...)org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
2018-02-08T10:58:42,448 INFO    Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@f0ea28, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@16efaab,(...)org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener@9604d9]
2018-02-08T10:58:42,521 INFO    Refreshing org.springframework.context.support.GenericApplicationContext@173f9fc: startup date [Thu Feb 08 10:58:42 IST 2018]; root of context hierarchy
2018-02-08T10:58:42,606 INFO    JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2018-02-08T10:58:42,666 ERROR    Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@19aaa5] to prepare test instance [io.github.thenilesh.service.impl.FooServiceTest@57f43]
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'io.github.thenilesh.service.impl.FooServiceTest': Unsatisfied dependency expressed through field 'fooService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'io.github.thenilesh.service.FooService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    . . . 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192) [.cp/:?]
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'io.github.thenilesh.service.FooService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1493) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    ... 28 more
2018-02-08T10:58:42,698 INFO    Closing org.springframework.context.support.GenericApplicationContext@173f9fc: startup date [Thu Feb 08 10:58:42 IST 2018]; root of context hierarchy
Run Code Online (Sandbox Code Playgroud)

完整的堆栈跟踪

如果测试类用@SpringBootTest注释,那么它会创建整个应用程序上下文,包括数据库连接和许多不相关的bean,这显然是这个单元测试不需要的(那么它就不会是单元测试了!)。预期的是,只有FooService依赖的 Bean 才应该被实例化,除了被模拟的 Bean 之外@MockBean

Jus*_*tas 5

你应该使用@SpringBootTest(classes=FooServiceImpl.class).

正如注释类型 SpringBootTest中提到的:

公共抽象类<?> []类

用于加载 ApplicationContext 的带注释的类。也可以使用 @ContextConfiguration(classes=...) 指定。如果没有定义显式类,则测试将在回退到 SpringBootConfiguration 搜索之前查找嵌套的 @Configuration 类。

返回:用于加载应用程序上下文的带注释的类另请参阅:ContextConfiguration.classes()

默认: {}

这将只加载必要的类。如果不指定,它可能会加载数据库配置和其他会使您的测试变慢的内容。

另一方面,如果你真的想要单元测试,你可以在没有 Spring 的情况下测试这段代码 - 那么@RunWith(SpringRunner.class)注释@SpringBootTest就不是必需的。您可以测试FooServiceImpl实例。如果您有Autowired/注入的属性或服务,您可以通过 setter、构造函数或使用Mockito进行模拟来设置它们。