Why component scanning does not work for Spring Boot unit tests?

后端 未结 3 853
借酒劲吻你
借酒劲吻你 2021-01-13 19:20

The service class FooServiceImpl is annotated with @Service aka @Component which makes it eligible for autowiring. Why this class is not being pick

相关标签:
3条回答
  • 2021-01-13 19:53

    I had to solve a similar problem with a slight variation. Thought to share the details of that, thinking it might give choices to those who hit upon similar issues.

    I wanted to write integration tests with only necessary dependencies loaded up instead of all of the app dependencies. So I chose to use @DataJpaTest, instead of @SpringBootTest. And, I had to include @ComponentScan too to parse the @Service beans. However, the moment ServiceOne started using a mapper bean from another package, I had to specify the specific packages to be loaded with @ComponentScan. Surprisingly, I even had to do this for the second service that does not auto-wire this mapper. I did not like that because it leaves the impression to the reader that this service depends on that mapper when it is actually not. So I realized that the package structure for services needs to be fine-tuned further to represent the dependencies more accurately.

    To summarise, instead of @SpringBootTest, a combination of @DataJpaTest+@ComponentScan with package names can use to load just the layer-specific dependencies. This might even help us to fine-tune the design to represent your dependencies more accurately.

    Design before

    1. com.java.service.ServiceOneImpl

    @Service
    public class ServiceOneImpl implements ServiceOne {   
      @Autowired
      private RepositoryOne repositoryOne;    
      @Autowired
      private ServiceTwo serviceTwo;      
      @Autowired
      private MapperOne mapperOne;
    }
    

    2. com.java.service.ServiceTwoImpl

    @Service
    public class ServiceTwoImpl implements ServiceTwo {   
      @Autowired
      private RepositoryTwo repositoryTwo;    
    }
    

    3. ServiceOneIntegrationTest

    @RunWith(SpringRunner.class)
    @DataJpaTest
    @ComponentScan({"com.java.service","com.java.mapper"})
    public class ServiceOneIntegrationTest {
    

    4. ServiceTwoIntegrationTest.java

    @RunWith(SpringRunner.class)
    @DataJpaTest
    @ComponentScan({"com.java.service","com.java.mapper"})
    public class ServiceTwoIntegrationTest {
    

    After fine-tuning the package names

    1. com.java.service.one.ServiceOneImpl

    @Service
    public class ServiceOneImpl implements ServiceOne {   
      @Autowired
      private RepositoryOne repositoryOne;    
      @Autowired
      private ServiceTwo serviceTwo;      
      @Autowired
      private MapperOne mapperOne;
    }
    

    2. com.java.service.two.ServiceTwoImpl

    @Service
    public class ServiceTwoImpl implements ServiceTwo {   
      @Autowired
      private RepositoryTwo repositoryTwo;    
    }
    

    3. ServiceOneIntegrationTest

    @RunWith(SpringRunner.class)
    @DataJpaTest
    @ComponentScan({"com.java.service","com.java.mapper"})
    public class ServiceOneIntegrationTest {
    

    4. ServiceTwoIntegrationTest.java

    @RunWith(SpringRunner.class)
    @DataJpaTest
    @ComponentScan({"com.java.service.two"}) // CHANGE in the packages
    public class ServiceTwoIntegrationTest {
    
    0 讨论(0)
  • 2021-01-13 19:55

    A unit test should test a component in isolation. You don`t even need to use the Spring Test context framework for a unit test. You can using mocking frameworks such as Mockito, JMock or EasyMock to isolate the dependencies in your component and verify the expectations.

    If you want a true integration test then you need to use the @SpringBootTest annotation on your test class. If you dont specify the classes attribute it loads the @SpringBootApplication annotated class. This results in production components like db connections being loaded.

    To eliminate these define a separate test configuration class which for example defines an embedded database instead of the production one

    @SpringBootTest(classes = TestConfiguration.class)
    public class ServiceFooTest{
    }
    
    @Configuration
    @Import(SomeProductionConfiguration.class)
    public class TestConfiguration{
       //test specific components
    }
    
    0 讨论(0)
  • 2021-01-13 19:58

    You should use @SpringBootTest(classes=FooServiceImpl.class).

    As it mentioned on Annotation Type SpringBootTest:

    public abstract Class[] classes

    The annotated classes to use for loading an ApplicationContext. Can also be specified using @ContextConfiguration(classes=...). If no explicit classes are defined the test will look for nested @Configuration classes, before falling back to a SpringBootConfiguration search.

    Returns: the annotated classes used to load the application context See Also: ContextConfiguration.classes()

    Default: {}

    This would load only necessary class. If don't specify, it may load a database configuration and other stuff which would make your test slower.

    On the other hand, if you want really want unit test, you can test this code without Spring - then @RunWith(SpringRunner.class) and @SpringBootTest annotations are not necessary. You can test FooServiceImpl instance. If you have Autowired/injected properties or services, you set them via setters, constructors or mock with Mockito.

    0 讨论(0)
提交回复
热议问题