The service class FooServiceImpl
is annotated with @Service aka @Component
which makes it eligible for autowiring. Why this class is not being pick
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.
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 {
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
}
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.