Java Spring Boot Test: How to exclude java configuration class from test context
Asked Answered
M

9

60

I have a Java web app with spring boot

When run test I need to exclude some Java config files:

Test config (need to include when test run):

@TestConfiguration
@PropertySource("classpath:otp-test.properties")
public class TestOTPConfig { }

Production config (need to exclude when test run):

 @Configuration
 @PropertySource("classpath:otp.properties")
 public class OTPConfig { }

Test class (with explicit config class):

@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestAMCApplicationConfig.class)
public class AuthUserServiceTest { .... }

Test config:

@TestConfiguration
@Import({ TestDataSourceConfig.class, TestMailConfiguration.class, TestOTPConfig.class })
@TestPropertySource("classpath:amc-test.properties")
public class TestAMCApplicationConfig extends AMCApplicationConfig { }

Also have class:

@SpringBootApplication
public class AMCApplication { }

When test is running OTPConfig used, but I need TestOTPConfig...

How can I do it?

Ministry answered 27/9, 2016 at 16:23 Comment(1)
Note that for efficiency in both development time and running time, it's usually best to list only the particular configuration classes you want to include.Aili
J
31

Typically you would use Spring profiles to either include or exclude Spring beans, depending on which profile is active. In your situation you could define a production profile, which could be enabled by default; and a test profile. In your production config class you would specify the production profile:

@Configuration
@PropertySource("classpath:otp.properties")
@Profile({ "production" })
public class OTPConfig {
}

The test config class would specify the test profile:

@TestConfiguration
@Import({ TestDataSourceConfig.class, TestMailConfiguration.class,    TestOTPConfig.class })
@TestPropertySource("classpath:amc-test.properties")
@Profile({ "test" })
public class TestAMCApplicationConfig extends AMCApplicationConfig {
}

Then, in your test class you should be able to say which profiles are active:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestAMCApplicationConfig.class)
@ActiveProfiles({ "test" })
public class AuthUserServiceTest {
  ....
}

When you run your project in production you would include "production" as a default active profile, by setting an environment variable:

JAVA_OPTS="-Dspring.profiles.active=production"

Of course your production startup script might use something else besides JAVA_OPTS to set the Java environment variables, but somehow you should set spring.profiles.active.

Jimenez answered 27/9, 2016 at 17:6 Comment(1)
As test code should not affect production code, I would prefer a solution where I do not have to mark production code as such.Burck
S
27

You can also just mock the configuration you don't need. For example:

@MockBean
private AnyConfiguration conf;

Put it into your test class. This should help to avoid that the real AnyConfiguration is being loaded.

Slake answered 18/7, 2020 at 22:0 Comment(4)
@SpringBootTest will scan and load the @Configuration before this has any effect...Immigration
It looks like for some people it works and for some it does not. Maybe that has to do with naming and overriding?Slake
It looks like random behavior. In my laptop environment, @MockBean overrides the original bean. In our CI environment, the original bean is running. Could not find any relevant difference between the environments, the code is the same and the active profiles are the same.Mera
I think it prevents the class AnyConfiguration from bean registered as a bean itself and does not execute the injections. But still the methods annotated with @Bean are executed.Bernadinebernadotte
D
16

You can also use @ConditionalOnProperty like below:

@ConditionalOnProperty(value="otpConfig", havingValue="production")
@Configuration
@PropertySource("classpath:otp.properties")
public class OTPConfig { }

and for tests:

@ConditionalOnProperty(value="otpConfig", havingValue="test")
@Configuration
@PropertySource("classpath:otp-test.properties")
public class TestOTPConfig { }

Then specify in your main/resources/config/application.yml

otpConfig: production

and in your test/resources/config/application.yml

otpConfig: test
Dews answered 19/1, 2018 at 12:40 Comment(3)
For @ConditionalOnMissingProperty see hereCleopatra
Putting environment specific Strings in source code is awkward. I see it sometimes, always hurts my eyes. Separation of concerns: bad idea to use this in order to fix tests (!).Tillo
It doesn't look so bad if you don't name it "test" and give it the name "custom.property.disabled", instead ;-)Batson
E
11

Create the same class as an "useless" mirror in test folder, for example:

In src you have /app/MyConfig.java

Create /app/MyConfig.java in test folder with @Configuration and leave empty body inside it.

Essentiality answered 14/10, 2022 at 21:3 Comment(5)
This actually worked, if the Test config was used in conjunction with @ContextConfiguration.Kaveri
This works for me. Thank you! The empty bean defined for test will override the normal bean for application.Marengo
This works but causes Redeclaration Error in Intellij IDEA. The build process still works though. Is there anyway to suppress such error?Neva
@YohanesGultom try this declaration in application.yaml: spring.main.allow-bean-definition-overriding=trueEssentiality
Only solution that worked without touching production code.Burck
S
8

Additionally, for excluding auto configuration:

@EnableAutoConfiguration(exclude=CassandraDataAutoConfiguration.class)
public class // ...
Scriptorium answered 18/1, 2021 at 7:42 Comment(2)
this does not work with @SpringBootTestEmirate
@KirillMikhailov it works for meHonk
F
8

Set a profile during tests and load OTPConfig only if the profile is not active.

@Profile("!test")
@Configuration
class OTPConfig { ... }

@SpringBootTest(properties = ["spring.profiles.include=test"])
public class AuthUserServiceTest { .... }
Faitour answered 9/12, 2022 at 10:42 Comment(2)
Tests should not affect production codeTriiodomethane
I've found this to be one of the simplest solutions, yes it mentions "test" outside of test config.. but it is absolutely minimal and avoids the need to explicitly activate one or more 'production' profiles in all other run scenarios..Nelan
S
3

SpringBoot 3. It works for me, now we exclude two configs from the context (SomeConfig.class, AlsoOneConfig.class)

@MockBean(classes = {SomeConfig.class, AlsoOneConfig.class})   
@TestConfiguration
public class YourTestConfig {
  .....
  //put here some beans if you need
}

and now we can use this test config, like that

@SpringBootTest(classes = {YourTestConfig.class})
class ComponentTest {
 // put here your tests
}
Sankaran answered 29/1 at 15:1 Comment(2)
Thanks. You can also place the mock bean annotation directly in the test classAdair
Yes, correct, depends on your needs.Sankaran
H
-1

The easiest way that I use -

  1. Put @ConditionalOnProperty in my main source code.
  2. Then define a test bean and add it as a part of the test configuration. Either import that configuration in the main test class or any other way. This is only needed when you need to register a test bean. If you do not need that bean then go to next step.
  3. Add a property in application.properties under src/test/resources to disable the use of configuration class that is present in the main folder.

Voila!

Heliotropism answered 24/6, 2019 at 21:16 Comment(1)
easiest way will be when you add some code exampleMetsky
A
-2

You can use @ConditionalOnMissingClass("org.springframework.test.context.junit4.SpringJUnit4ClassRunner") in Configuration class.SpringJUnit4ClassRunner can be replaced by any class which only exits in test environment.

Atombomb answered 2/8, 2018 at 9:11 Comment(2)
You can do this, and it would probably work, but it feels clumsy and error-prone. It won't be obvious to someone looking at the code in future what it's doing and why, which will make it likely that they will remove it, and as tests will presumably continue to pass, they won't realise their mistake until much later. It's also generally not a great idea to include code specific for tests in production code; much better to have the production code work without exceptions, and put the exceptions in your test code.Shannanshannen
Yes.I quite agree with you.But it is simple,and give it a comment will make up it's readability. But as you say," It's also generally not a great idea to include code specific for tests in production code",thank you. @ShannanshannenSkip

© 2022 - 2024 — McMap. All rights reserved.