Search code examples
spring-bootjunitintegration-testing

Spring Boot: pruning application context for integration testing


JUnit Jupiter 5.7.2 and Spring Boot 2.6.3 here. I have the following service class:

@Service
public class PasswordService implements PasswordEncoder {

    private BCryptPasswordEncoder passwordEncoder;
    private PasswordGenerator passwordGenerator;
    private CharacterCharacteristicsRule passwordCharacteristicsRule;
    private Integer minPasswordLength;

    @Autowired
    public PasswordService(
            BCryptPasswordEncoder passwordEncoder,
            PasswordGenerator passwordGenerator,
            CharacterCharacteristicsRule passwordCharacteristicsRule,
            @Value("${myapp.users.passwords.min-length}") Integer minPasswordLength) {
        this.passwordEncoder = passwordEncoder;
        this.passwordGenerator = passwordGenerator;
        this.passwordCharacteristicsRule = passwordCharacteristicsRule;
        this.minPasswordLength = minPasswordLength;
    }

    public String generatePassword() {
        return passwordGenerator.generatePassword(minPasswordLength, passwordCharacteristicsRule.getRules());
    }

    public String protectPassword(String plaintextPassword) {
        return passwordEncoder.encode(plaintextPassword);
    }

    @Override
    public String encode(CharSequence rawPassword) {
        return protectPassword(rawPassword.toString());
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return protectPassword(rawPassword.toString()).equals(encodedPassword);
    }

}

I have a unit test for each method, but because its generatePassword() method is so important and depends so heavily on its dependencies, I also want to create an integration test for it that actually autowires/injects it with real instances of all of its dependencies.

My best attempt:

@SpringBootTest
public class PasswordServiceIT {

    @Inject
    private PasswordService passwordService;

    @Test
    public void when_generatePassword_should_generate_valid_password() {

        String password = passwordService.generatePassword();
        System.out.println(String.format("Password is: %s", password));

    }


}

When I run this test I am getting Failed to load ApplicationContext because, in my application.yml, I have the following:

server:
  port: ${SERVER_PORT}
  error:
    whitelabel:
      enabled: false

And when the Application Context tries to spin up, it can't inject ${SERVER_PORT} because thats typically set by environment variable.

I don't want to hardcode it or set a default in the YAML like ${SERVER_PORT:9200}, etc. I just don't like hardcoding configs (especially in other places, like where credentials are stored) in these YAML files.

Is there a way to "prune" which parts of the application context get loaded? This entire "Password Generation system" doesn't need to talk to any databases, remote services, shouldn't need a server port to start from, etc.


Solution

  • you can create application.yml/properties file under src/test/resources to override the properties values for integration test cases.

    The properties from src/test/resources -> application.yml will be loaded for only integration tests

    Now we'll override properties by putting the property file in the test resources. This file must be on the same classpath as the default one.

    Additionally, it should contain all the property keys specified in the default file. Therefore, we'll add the application.properties file into the src/test/resources:

    src/test/resources -> application.yml
    
    server:
      port: 9200
      error:
        whitelabel:
          enabled: false
    

    If you don't want every test cases to run on 9200 port, you can specify to use random port using annotation and i would highly recommend executing tests on random port

    @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)