Can You Enable Bean Overriding On A Single Test Class? - java

One of my test classes has conflicts between the test beans and the production beans.
I can fix that by adding spring.main.allow-bean-definition-overriding=true to my test application.properties.
However, this now applies to all the tests.
Is it possible to apply this to a single test?
e.g. with an annotation?

Yes, you can set properties in the SpringBootTest annotation.
#SpringBootTest(properties =
{ "spring.main.allow-bean-definition-overriding = true" })
class TestClass{}

Related

Spring boot test not using overridden bean in test in spite of marking #Primary

We have a bean CompanyProcessor.
In spring boot integration test, we want to test an exception scenario when a method in this bean throws Exception.
Therefore we have created an overriding bean TestCompanyProcessor which extends CompanyProcessor.
TestCompanyProcessor is marked #Primary. When we run just this test class, the test runs fine and the overridden bean is used as expected.
But then this test class is run with several other test classes together, each marked as #SpringBootTest, we see that this test fails at first instance as instead of using the overridden bean, it uses this original bean. We have automatic retries on failure for upto 3 retries. On the second or third retry, it somehow finds the correct overridden primary bean and the test passes.
Is there any reason, why it finds the original bean at first instance and then finds the correct overridden bean on subsequent retries when run alongside several other test classes.
The overriden bean is defined as follows.
#Primary
#Component
#ConditionalOnExpression("'${test.company.processor}' == 'true'")
class TestCompanyProcessor
and on the test class we have
#TestPropertySource(properties = {"test.company.processor=true"})
class TestCompanyProcessorTest {
}
P.S.
we saw the same behaviour when we used #Mockbean annotation
You sees this behaviour probably if you have multiple Spring tests that share the same context.
Spring is caching your test application context between tests. This is the default behaviour as the instanciation of the objects in a spring context may take some time.
If you have different beans in different tests you should mark tests that change the ApplicationContext (like the TestCompanyProcessorTest class) with an #DirtiesContext annotation.
Further info on caching:
https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#testing-ctx-management
DirtiesContext example:
https://www.baeldung.com/spring-dirtiescontext

Trouble executing a unit test that should ignore Spring annotations on the unit under test

I'm trying to execute a unit test for a service class that has an #Async("asyncExecutor") annotated method. This is a plain JUnit test class with no Spring runners and no intention of using Spring at all in the unit test. I get the exception,
BeanFactory must be set on AnnotationAsyncExecutionAspect to access qualified executor 'asyncExecutor'
Where asyncExectuor is the name of the bean to be used during normal execution. My configuration class looks like this and I solved that previous error message at runtime by adding the mode = AdviceMode.ASPECTJ portion. This service works at runtime without issue in an Async way.
#Configuration
#EnableAsync(mode = AdviceMode.ASPECTJ)
public class AsyncConfiguration {
#Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
...
}
}
I don't understand why the Spring context is being constructed at all in the unit test. The test class is simply annotated #Test on the methods with no class annotations and no mention of Spring. I was hoping to unit test this service class method as a regular method ignoring the async nature, but the annotation is being processed for some reason.
I'm contributing to a much larger gradle + Spring 4 project that I'm not fully knowledgeable about. Is there anything I should be looking for to see if a Spring context is being created by default for all tests?
As you noticed, Spring context is not loaded, that is the reason of your error. Try to initialize Spring context in your test by adding #RunWith and #ContextConfiguration annotations

SpringBootTest - how to assert context not loads and change properties at test level?

I rely on #SpringBootTest heavily when testing application configuration. App properties can be complex, having default values and non-trivial validations. For example:
prop:
ports: 1205,2303,4039
fqdn: ${server.host}:${ports[0]}/${path}
#Configuration
SomeConfigClass{
#Value{"${ports}:{#collections.emptyList}"}
List<Integer> ports;
...
}
When testing such apps, I bring up a full or partial application context without mocks, as there is complexity in the context and validation itself - mocks don't capture this. Unfortunately, there are two limitations I keep finding with this pattern:
How can we test that bad configurations fail to load?
Imagine testing that the port in invalid because it is not on the restricted range of 500 - 1500.
#SpringBootTest(
classes = {SomeConfigClass.class},
properties = "port=9000"
)
public class BadConfigTest{
#Test(expected = ApplicationContextFailedException.class)
public void WHEN_port_9000_THEN_appcontext_fails_to_load() {}
}
Since the test framework loads after the application context, there appears to be no way to test that an app context fails to load. For now I actually write the tests, manually confirm they fail, and then annotation with #Ignored so they are not lost.
How to change properties at the test method, rather than class, level?
#SpringBootTest is a class annotation, meaning application properties are bound at the test-class level. This results in needing a test class for many sets of properties and bloats the test suite. For example, I'll end up with test classes like:
ConfigPropertiesAllValidTest
ConfigPropertiesNoneSetTest
ConfigPropertiesSomeValidSomeNotTest
Where each of these only has one or two test cases. Preferably, there'd be a single ConfigPropertiesTest class with different props for each test. Is this possible?
Again - I want to avoid mocks as they don't capture the non-trivial context autoconfiguration performed by Spring at runtime.
We ended up using the ApplicationContextRunner described in this document:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-auto-configuration.html#boot-features-test-autoconfig
You can use the #ActiveProfiles annotation along with the #SpringBootTest annotation to load properties for different profiles. This is class level, so will only help for case 1 of your question.
#SpringBootTest(classes = {SomeConfigClass.class})
#ActiveProfiles("badconfigtest")
public class BadConfigTest{
...
}
Then have an application-badconfigtest.properties with your bad config.
I don't think you'll find a way of changing properties between test methods in the same class. You can use #DirtiesContext to reload the application context, but I've not seen a way to use different property files. I guess you could inject the values into the config classes that have already loaded the properties.

How can I use #IfProfileValue to test if a Profile is active?

So confusingly #IfProfileValue has nothing to do with #Profile or #ActiveProfiles. #Profile tests to see if a profile is active, #ActiveProfiles sets them as active, and #IfProfileValue allows you to check things in Spring Environment. Wut? I'd deprecate all of them and add new ones #IfEnvironment, #IfProfile, and #ActivateProfiles.
Commentary aside, how can I use #IfProfileValue to detect whether i have a profile active? I am not using Spring Boot on this project, at this time. Answers should show code, and we will assume that I want the test to run if the profile is activated as #ActiveProfiles( "test" ).
I tried #IfProfileValue(name = "activeProfiles", value = "test") but that seems to have the test skipped, which means it's not matching. I'm going to speculate the problem may have to do with the fact that ActiveProfiles is a Collection.
So confusingly #IfProfileValue has nothing to do with #Profile or
#ActiveProfiles.
That's correct, and I explained this in detail here: https://stackoverflow.com/a/23627479/388980
... which I'm assuming you have already seen, since you commented on my answer yesterday.
The reason that #IfProfileValue has nothing to do with #Profile or #ActiveProfiles is due to the evolution of the framework. See below for further details.
#Profile tests to see if a profile is active,
#ActiveProfiles sets them as active, and #IfProfileValue allows
you to check things in Spring Environment.
These statements are not entirely correct, especially the last part.
#Profile is used to selectively enable a component (e.g., #Service, etc.), #Configuration class, or #Bean method if one of the named bean definition profiles is active in the Spring Environment for the ApplicationContext. This annotation is not directly related to testing: #Profile should not be used on a test class.
#ActiveProfiles is used to designate which bean definition profiles (e.g., those declared via #Profile) should be active when loading an ApplicationContext for an integration test.
#IfProfileValue does not allow you to check things in the Spring Environment. I'm not sure why you are assuming this, since none of the documentation in the Spring Framework states that. As I stated in the aforementioned thread:
Please note that #IfProfileValue was introduced in Spring Framework 2.0, long before the notion of bean definition profiles, and #ActiveProfiles was first introduced in Spring Framework 3.1.
In the aforementioned thread, I also pointed out the following:
The term 'profile' is perhaps misleading when considering the semantics for #IfProfileValue. The key is to think about 'test groups' (like those in TestNG) instead of 'profiles'. See the examples in the JavaDoc for #IfProfileValue.
how can I use #IfProfileValue to detect whether i have a profile active?
That depends, and... I'm assuming you mean bean definition profile when you say "profile".
If you're using #ActiveProfiles to set the bean definition profiles for your tests, you cannot currently use #IfProfileValue to determine if a bean definition profile is active, since the bean definition profiles configured via #ActiveProfiles are set directly in the test's ApplicationContext and not as a Java system property.
However, if you are setting the bean definition profiles only via the spring.profiles.active system property, then it would be possible to use #IfProfileValue to determine if a bean definition profile is active, since #IfProfileValue in fact works with system properties. For example, you could then use the following:
#IfProfileValue(name = "spring.profiles.active", value = "test")
I tried #IfProfileValue(name = "activeProfiles", value = "test") but
that seems to have the test skipped, which means it's not matching.
That won't work since activeProfiles is the incorrect property name. The correct system property name is spring.profiles.active. See AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME for details.
The fact that #IfProfileValue does not work in harmony with #ActiveProfiles is a known issue to the Spring team. Please consult the following JIRA issues for further details and to join in on the discussions if you'd like.
https://jira.spring.io/browse/SPR-7754
https://jira.spring.io/browse/SPR-8982
https://jira.spring.io/browse/SPR-11677
Hope this clarifies the situation for you!
Sam (author of the Spring TestContext Framework)
Sam nailed it. (As well as the fact this was accepted and answered years back)
One thing to add is that if you'd like to pass System Properties through to your test, having them propagate through to the JVM if you are using a build tool like gradle may require an additional step.
//build.gradle
tasks.withType(Test) {
systemProperties System.properties
}
And then business as usual in your integration test
//MyIntegrationTest.class
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles("integration")
#IfProfileValue(name = "run.integration.tests", value = "true")
public class MyIntegrationTest {
#Test
public void myTest(){ ... }
}
Finally you can execute your tests from the terminal with the property you specified.
$> ./gradlew clean test -Drun.integration.tests=true
The thing I like most about #IfProfileValue over grabbing the System.property and checking assumeTrue/False manually is that no Spring Context is loaded (or flyway/other migrations you may have) keeping unit tests fast.
Unfortunately, from my experience, test dependency on #IfProfileValue
#Test
#IfProfileValue(name="spring.profiles.active", values={"test"})
Will work only when you set spring.profiles.active as a JVM property, as:
-Dspring.profiles.active="test"
#IfProfileValue just ignores spring.profiles.active from application.properties/yml.
When using junit5 it is possible to conditionally enable the test based on the active profile:
#EnabledIf(expression =
"#{environment.acceptsProfiles(T(org.springframework.core.env.Profiles).of('someprofile'))}",
loadContext = true)
class MyTest {
...
}
This checks if someprofile is active and unlike checking for spring.profiles.active property works event if there are more than one profile enabled. This checks if someprofile is among currently active profiles.
In order to make this more readable it is possible to use spring meta-annotation. Define annotation first
#Target({ ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#EnabledIf(expression =
"#{environment.acceptsProfiles(T(org.springframework.core.env.Profiles).of('someprofile'))}",
loadContext = true)
public #interface EnabledIfSomeProfile {
}
And then use it on test classes:
#EnableIfSomeProfile
class MyTest {
...
}

Spring autowiring dependencies after each junit test

I'm having a problem whereby spring is re-creating my beans and re-auto wiring after each junit test has been run. I only want spring to do this once, on the post construct of the test class, which is what I've configured it to do.
I've also tried setting the #DirtiesContext class mode to AFTER_CLASS, but this still didn't solve the issue.
Any ideas?
Cheers.
You can create a class:
#SpringJUnitConfig(classes = {ConfigClassOne.class, ConfigClassTwo.class, ConfigClassThree.class})
public abstract class ModelTest {}
and extend it in the test classes. Beans will not be recreated because the application contexts are already mentioned in the annotation.

Categories

Resources