I've been running into #ComponentScan issues with #Configuration classes for tests -- namely, the #ComponentScan is pulling in unintended #Configuration during integration tests.
For example, say you've got some global config in src/main/java which pulls in components within com.example.service, com.example.config.GlobalConfiguration:
package com.example.config;
...
#Configuration
#ComponentScan(basePackageClasses = ServiceA.class)
public class GlobalConfiguration {
...
}
It's intended to pull in two services, com.example.services.ServiceA and com.example.services.ServiceB, annotated with #Component and #Profile("!test") (omitted for brevity).
Then in src/test/java, com.example.services.ServiceATest:
package com.example.services;
...
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = ServiceATest.ServiceATestConfiguration.class)
public class ServiceATest {
...
#Configuration
public static class ServiceATestConfiguration {
#Bean
public ServiceA serviceA() {
return ServiceA(somemocking...);
}
}
}
And also com.example.ServiceBIntegrationTest, which needs to pull in GlobalConfiguration.class in order to be an integration test, but still avoids pulling in dangerous implementations with #ActiveProfiles("test"):
package com.example.services;
...
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles("test")
#ContextConfiguration(classes = {GlobalConfiguration.class, ServiceBIntegrationTest.ServiceBIntegrationTestConfiguration.class})
public class ServiceBIntegrationTest {
...
#Configuration
public static class ServiceBIntegrationTestConfiguration {
#Bean
public ServiceB serviceB() {
return ServiceB(somemocking...);
}
}
}
The obvious intention of the ServiceBIntegrationTest is to pull in the complete src/main/java application configuration via GlobalConfiguration, exclude dangerous components via #ActiveProfiles("test") and replace those excluded components with its own implementations. However, during tests the namespace of src/main/java and src/test/java are combined, so GlobalConfiguration's #ComponentScan finds more in the classpath than it normally would -- namely, the ServiceA bean defined in ServiceA.ServiceATestConfiguration. That could easily lead to conflicts and unintended results.
Now, you could do something on GlobalConfiguration like #ComponentScan(..., excludeFilters= #ComponentScan.Filter(type = FilterType.REGEX, pattern = "\\.*(T|t)est\\.*")), but that has issues of its own. Relying on naming conventions is pretty brittle; still, even if you backed out a #TestConfiguration annotation and used FilterType.ANNOTATION, you'd effectively be making your src/main/java aware of your src/test/java, which it shouldn't be, IMO (see note below).
As it stands, I've solved my problem by using an additional profile. On ServiceA, I add a unique profile name -- so that its profile annotation becomes something like #ActiveProfiles("test,serviceatest"). Then, on ServiceATest.ServiceATestConfiguration I add the annotation #Profile("serviceatest"). This effectively limits the scope of ServiceATestConfiguration with relatively little overhead, but it seems like either:
a) I am using #ComponentScan incorrectly, or
b) There should be a much cleaner pattern for handling this problem
Which is it?
note: yes, the app is test-aware because it's using #Profile("!test"), but I'd argue making the application slightly test-aware to defend against improper resource usage and making it test-aware to ensure correctness of tests are very different things.
I see you are trying to fake Spring beans during integration test. If you combine #Profile and #ActiveProfiles annotation with #Primary annotation, most of your headaches should go away and you shouldn't need to mark production beans with #Profile("!test").
I wrote a blog post on the topic with Github examples.
Reaction on comment:
By package structure. Component scan scans all packages within current package and sub-packages. IF you don't want to scan beans, just amend your package structure the way that bean wouldn't be under your component scan umbrella.
Spring doesn't differentiate packages from src/test/java or src/main/java. Trying to exclude production beans with #Profile("!test") is design smell. You should avoid it. I would suggest to give a chance to approach from mentioned blog.
Notice that when you override the bean with #Primary annotation, you may need to use #DirtiesContext annotation to have clean sheet for other tests.
Related
This question is a sequel to Can I use code to control the dependency resolution decisions made by ApplicationContext in Spring Boot?
The accepted answer is to define a nested class within each #SpringBootTest test fixture class, to annotate it with #TestConfiguration and to define within it a factory method for each bean that needs to be resolved. The influence of the nested classes is scoped to the test fixture affecting all of the tests in the fixture but not affecting tests defined in other fixtures.
This provides fine grained control over the dependencies injected into the components when running the tests in each test fixture.
The problem with this approach is that it requires adding a nested resolver class within each test fixture class.
This is not scalable.
Consider a project with 10 test fixtures. 9 of these use the same injected dependencies and only the 10th requires a different implementation for only one particular interface.
In this case I would need to copy the test configuration class into 9 test fixture classes and use a second configuration class for only the 10th test.
I need a more scalable way to do this. For instance, in the case above, I would like to be able to define two configuration classes, one for each of the two configurations used by the test fixtures. Then I would like to be able specify for each test fixture which of the two configuration classes should be used.
I have tried:
I tried importing the nested configuration class of one text
fixture into another test fixture using the #Import annotation into
the latter, but when doing so, the configuration class is ignored in
the latter.
I also tried moving a nested configuration class to the
upper level so that it might be used for all test fixtures that do
not explicitly define a different one as a nested class, but in this
case the configuration class is ignored by all test fixtures.
So in summary I am looking for an efficient way that would allow me to write each configuration class only once and then to selectively apply one to each SpringBootTest class without needing to copy it.
After some experimentation I have reached the following solution.
I will add all the details summarizing what I learned in the previous question too.
Background
We have two interfaces: IClient and IServer
There are two implementations of IClient: RealClient and MockClient.
There are two implementations of IServer: RealServer and MockServer.
Requirements
Production code (in main/java) should use the Real implementations of both.
Test Fixtures (annotated with #SpringBootTest in test/java)
InterfaceTests defines tests that should use a MockServer and a MockClient
ClientTests defines tests that should use a MockServer and RealClient to test the RealClient.
ServerTests defines tests that should use a MockClient and a RealServer to test the RealServer.
IntegrationTests defines tests that should use a RealServer and a RealClient
From the above it is clear that there are four combinations of mock/real client/server and each combination is needed in some area of the code.
Solution
This solution makes use of the #Configuration and #TestConfiguration annotations in order to implement these requirements with no code duplication.
Do NOT annotate interfaces nor their implementations with #Component
Under main/java implement a configuration class as follows:
#Configuration
public class RealInjector {
#Bean
public IServer createServer(){
return new RealServer();
}
#Bean
public IClient createClient(){
return new RealClient();
}
}
Under test/java implement these three test configuration classes
#TestConfiguration
public class AllMockInjector {
#Bean
public IServer createServer(){
return new MockServer();
}
#Bean
public IClient createClient(){
return new MockClient();
}
}
#TestConfiguration
public class MockServerInjector{
#Bean
public IServer createServer(){
return new MockServer();
}
#Bean
public IClient createClient(){
return new RealClient();
}
}
#TestConfiguration
public class MockClientInjector{
#Bean
public IServer createServer(){
return new RealServer();
}
#Bean
public IClient createClient(){
return new MockClient();
}
}
Annotate the InterfaceTests test fixture as follows:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {AllMockInjector.class})
public class InterfaceTests { ... }
Annotate the ClientTests test fixture as follows:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {MockServerInjector.class})
public class ClientTests { ... }
Annotate the ServerTests test fixture as follows:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {MockClientInjector.class})
public class ServerTests { ... }
Annotate the IntegrationTests test fixture as follows:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {RealInjector.class})
public class IntegrationTests { ... }
Finally
In order for the test configuration classes to override the RealInjector configuration class from main/java we need to set the property:
spring.main.allow-bean-definition-overriding=true
One way to do this is to annotate each of the above test fixtures as follows:
#SpringBootTest(properties = ["spring.main.allow-bean-definition-overriding=true"])
class TestFixture { ... }
but this is quite verbose especially if you have many test fixtures.
Instead you can add the following in the application.properties file under test/resources:
spring.main.allow-bean-definition-overriding=true
You may also need to add it in application.properties under main/resources too.
Summary
This solution gives you fine grained control over the implementations that are injected into your code for production and for tests. The solution requires no code duplication or external configuration files (apart from one property in test/resources/application.properties).
We have an application that relies on Spring Boot 2.0. We are in the process of migrating it to JDK11 from JDK8. This also enabled us to update Spring Boot from 2.0 to 2.1. After reading through the changelog, it appeared there was any major change that needed for us.
Now the problem lies in where some test classes are annotated with both #SpringBootTest and #DataJpaTest. As per this and as well as the documentation, we are not supposed to use both together and instead we changed #DataJpaTest to #AutoConfigureTestDatabase. Here is how the code is:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {A.class, B.class}, properties = {
"x=xxx",
"y=yyy"
})
#AutoConfigureTestDatabase // Used to be #DataJpaTest
#EnableJpaRepositories("com.test")
#EntityScan("com.test")
public class Test {
#TestConfiguration
public static class TestConfig {
// Some beans returning
}
// Tests
}
Now, we end up with the following error:
NoSuchBeanDefinitionException: No bean named 'entityManagerFactory' available
So as per this answer, we did something like this:
#EnableJpaRepositories(basePackages="com.test", entityManagerFactoryRef="entityManagerFactory")
Even after this we still end up with the same error. Is this the right way to remove #DataJpaTest? Or do we need to remove #SpringBootTest and do something else? Any sort of guidance is much appreciated.
The testclass is annotated with #DataJpaTest and #ContextConfiguration
#RunWith(SpringRunner.class)
#DataJpaTest
#ContextConfiguration(locations = { "classpath:test-context.xml" })
public abstract class AbstractTestCase {
protected static final Logger LOG = LoggerFactory.getLogger(AbstractTestCase.class);
}
We defined a test-context.xml. This is because the testmodule is isolated from all other modules (multi maven module project). In the test-context.xml we defined the component-scan for the base-package.
<context:component-scan base-package="de.example.base.package" />
Let's say I'm testing a repository :
#RunWith(SpringRunner.class)
#SpringBootTest
public class UserRepositoryTest {
#Test
(...)
#Test
(...)
}
I'm ok that spring loads other repositories, but I'm not ok it loads the embedded Tomcat, the services, the controllers, ... every time I launch one of these JUnit.
What is the simplest way to achieve this?
I've tried to put some inner #Configuration class with a #ComponentScan limited to my repository package but it didn't work (it was just ignored).
Use the annotation #DataJpaTest instead of #SpringBootTest. It only loads the persistence related part of Spring.
#RunWith(SpringRunner.class)
#DataJpaTest
public class UserRepositoryTest {
#Test
(...)
#Test
(...)
}
You will find a detailed solution here
If you have some usage of JdbcTemplate then take a look to this answer
It looks like there is not one single answer to this question.
Of course, for JPA repositories, Lore answer is the best : use #DataJpaTest (or #JdbcTest for my use case). But be also sure to use "#AutoConfigureTestDatabase(replace = Replace.NONE)" if your test data is in your database and not in some in-memory one.
Also there is a special chapter talking about this in Spring doc :
Spring Boot’s auto-configuration system works well for applications
but can sometimes be a little too much for tests. It often helps to
load only the parts of the configuration that are required to test a
“slice” of your application.
source : https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-testing-autoconfigured-tests
But it doesn't show all you can/need to do.
For example, I had a smtpClientService to test.
To test this service, alone in its own layer, I had to do these specific adaptations (if I omit "#AutoConfigureWebClient", I won't get RestTemplateBuilder injected) :
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureWebClient
public class smtpClientServiceTest {
#Autowired
SmtpClientService service;
#Configuration
#Import(SmtpClientConfig.class)
#ComponentScan(basePackageClasses = SmtpClientService.class)
static class TestConfiguration {
}
#Test
public void testSendMessage() {
(...)
}
}
When I setup Spring with XML a can override component definitions in XML files that are loaded later.
It's very usefull for tests - I create default config set and than load it with addition test configuration that replaces some of components with specials (stubs, mocks, and so on).
Now i start to migrate to annotation based configurations and it causes some problems.
The direct way to use annotations is auto-discovering of packages with #Component
So I have
#Configuration
#ComponentScan({"some.pack1", "some.pack2"})
public class ProductConfig{}
And when
#Configuration
#Import({ProductConfig.class})
#ComponentScan({"test.pack"})
public class TestConfig{}
But it will cause conflict if I try to override components in test.pack
And what I can do?
After some investigations where are 3 answers with some issues on them
Worst - i can use #Filter on ComponentScan - it's worst way,
i must not import existed config (that can has some additional beans)
i must rescan all components, and explicitly define set of filters
i can use #Profile and activeProfiles - it's better, while it's more sophistical, implict, but
it means that i must to know at product classes that they can be disabled in some tests
not to use #ComponentScan on override Config and using #Bean insted of it
it's maybe well on test configurations, but it means that I lost ability to use #Component annotation
use setParent on contexts - it works well, but
it's explicit operation on implementation of ApplicationContext not on interface
it's not hard to setup if overriding services has #Autwire dependency on some components from overriden config - require manual register and refresh
What is best and standard way to override conigurations??? When I used XML-based it was not a problem...
#profile plays a crucial role while implementing the testing strategy for your service/code.
For example, in development, you may have:
public interface DataSource{
public String getHost();
}
Default implementation is
#Component
#Profile("Prod")
public class DevDataSource implements DataSource {
public String getHost(){
// return actual value
}
And the implementation for component tests(Fake impl)
#Component
#Profile("test")
public class StubbyDataSource implements DataSource {
public String getHost(){
return "some-host"; // return mocked data
}
Now you can write a test here which can act as integration test, unit test and component tests (https://martinfowler.com/bliki/ComponentTest.html)
In that way, your testing strategy would be much more elegant, concise and easy to maintain. Just by changing the profile, the same test can point to different environments (real or fake).
Recently, Spring Boot added TypeExcludeFilters. One prominent use case is the SpringBootApplication annotation.
Before Spring Boot 1.4:
// ...
#ComponentScan
public #interface SpringBootApplication {
// ...
Since Spring Boot 1.4:
// ...
#ComponentScan(excludeFilters = #Filter(type = FilterType.CUSTOM,
classes = TypeExcludeFilter.class))
public #interface SpringBootApplication {
// ...
The main motivation seems to improve testing support in Spring, but I fail to get an intuitive understanding of what it does and in what situations it can be beneficial.
Can someone illustrate in a simple example how this new concept is intended to be used?
Background: The change came in Spring 1.4.0 with commit 513dec718fd3e7449ec76b6a916f4696d1942d5d:
Add a new TypeFilter specifically for excluding candidate components.
The filter is applied to #SpringBootApplication and allows tests to
dynamically contribute exclude filters so that specific classes of
component can be excluded.
See gh-5295
See gh-4901
One interesting example is #WebMvcTest, because it works thanks to a TypeExcludeFilter:
//...
#TypeExcludeFilters(WebMvcTypeExcludeFilter.class)
//...
public #interface WebMvcTest {
...
}
WebMvcTypeExcludeFilter ultimately implements TypeExcludeFilter, which is used to determine if a component/class should not be loaded for this test. Which ones are not included (are excluded)? Well WebMvcTypeExcludeFilter includes some types by default:
static {
Set<Class<?>> includes = new LinkedHashSet<>();
includes.add(ControllerAdvice.class);
includes.add(JsonComponent.class);
includes.add(WebMvcConfigurer.class);
...
DEFAULT_INCLUDES = Collections.unmodifiableSet(includes);
}
In essence, this WebMvcTypeExcludeFilter will match any class that is not "included". By matching the filter, the class will be excluded when loading the spring configuration, effectively applying "only configuration relevant to MVC tests" as stated by the JavaDoc.
Say for some reason (e.g. in integration tests) you don't want some beans (even marked with #Component or #Service) to be registered in application context.
This can be achieved by implementing TypeExcludeFilter and applying it to test class:
#SpringBootTest
#TypeExcludeFilters(YourTypeExcludeFilter.class)
public class YouIntegrationTest() {
For an example of how to implement TypeExcludeFilter look at the TestTypeExcludeFilter.