Search code examples
spring-bootjunitintegration-testing

Wiring real dependencies into Spring Boot integration test


Spring Boot integration testing 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:

public class PasswordServiceIT {

    @Inject
    private BCryptPasswordEncoder passwordEncoder;

    @Inject
    private PasswordGenerator passwordGenerator;

    @Inject
    private CharacterCharacteristicsRule passwordCharacteristicsRule;

    private PasswordService passwordService;

    @BeforeEach
    public void setup() {
        passwordService = new PasswordService(passwordEncoder, passwordGenerator, passwordCharacteristicsRule, 10);
    }

    @Test
    public void when_generatePassword_should_generate_valid_password() {

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

    }


}

Throws NPEs because passwordEncoder and passwordGenerator are being injected as null. Again I'm looking for the real BCryptPasswordEncoder and the real PasswordGenerator and the real CharacterCharacteristicsRule to be injected into my PasswordService instance here, so I can take it for a real test spin.

Can anybody spot where I'm going awry here? Thanks in advance!


Solution

  • Do not create an instance for your service PasswordService using the new keyword. If you do, all the @Inject/@Autowire members will be null. You need to inject the PassworService also.

    And annotate the test class with org.springframework.boot.test.context.SpringBootTest so when running the tests, a spring context will be created.

    So you test class will as follows.

    @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));
        }
    }
    

    You don't need to inject other classes unless you directly use it in your tests in this class.

    Note: For this, you need org.springframework.boot:spring-boot-starter-test should be in the test scope dependency.