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

此生再无相见时 提交于 2019-12-01 09:29:45

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.

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 {

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
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!