63

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?

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. Commented Sep 27, 2016 at 17:16

10 Answers 10

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.

Sign up to request clarification or add additional context in comments.

2 Comments

As test code should not affect production code, I would prefer a solution where I do not have to mark production code as such.
Just use "default" instead of "production", if you do so, you don't have to set it explicitily when running application on prod, like JAVA_OPTS="-Dspring.profiles.active=production". .
31

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.

4 Comments

@SpringBootTest will scan and load the @Configuration before this has any effect...
It looks like for some people it works and for some it does not. Maybe that has to do with naming and overriding?
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.
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.
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

3 Comments

For @ConditionalOnMissingProperty see here
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 (!).
It doesn't look so bad if you don't name it "test" and give it the name "custom.property.disabled", instead ;-)
14

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.

5 Comments

This actually worked, if the Test config was used in conjunction with @ContextConfiguration.
This works for me. Thank you! The empty bean defined for test will override the normal bean for application.
This works but causes Redeclaration Error in Intellij IDEA. The build process still works though. Is there anyway to suppress such error?
@YohanesGultom try this declaration in application.yaml: spring.main.allow-bean-definition-overriding=true
Only solution that worked without touching production code.
7

Additionally, for excluding auto configuration:

@EnableAutoConfiguration(exclude=CassandraDataAutoConfiguration.class)
public class // ...

1 Comment

this does not work with @SpringBootTest
6

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 { .... }

2 Comments

Tests should not affect production code
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..
1

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
}

2 Comments

Thanks. You can also place the mock bean annotation directly in the test class
Yes, correct, depends on your needs.
0

Adding an extra non-touching-production-code option.

Create a TypeExcludeFilter,

public class ExcludeConfigs extends TypeExcludeFilter
{
    @Override
    public boolean match(final MetadataReader metadataReader, final MetadataReaderFactory metadataReaderFactory) throws IOException
    {
       if (metadataReader.getClassMetadata().getClassName().equals(OTPConfig.class.getName()))
       {
            return true;
        }
        return false;
    }

    (*1)
}

And on the SpringBootTest class add the annotation

@TypeExcludeFilters(value = {ExcludeConfigs.class})

NOTES

*1: According to javadoc you should add hashCode and equals implementations, I tried with lombok ones and worked.

You can also add this class an inner at the SpringBootTest with correct reference and also works.

Source: answer from https://github.com/spring-projects/spring-boot/issues/15068#issuecomment-435774681.

Comments

-2

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!

1 Comment

easiest way will be when you add some code example
-3

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.

2 Comments

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.
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. @DaveyDaveDave

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.