Search code examples
javaspring-boottestingjwtspring-boot-test

How can I use env variables with Spring Boot Test?


In my Spring Boot Security application, I have stored my secret at file application.properties and it works just fine.I can get my secret key like this:

@Value("${secret}")
private String secret;

But in order to testing it and don't hard code the secret password on my test, I need to generate my token and put it on my Authorization header (using mockMvc), but I can't access my secret env variable from test class, even if I insert my env variable at application.properties file of test paths.

How I'm generating token for my tests:

public class TokenForTest {
    public static String generateTokenForTest() {
        Date today = new Date();
        Date expireDate = new Date(today.getTime() + Long.parseLong("86400000"));
        return "Bearer " + Jwts.builder()
                .setIssuer("BeatSound API")
                .setSubject("1")
                .setIssuedAt(today)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS256, "secretKey")
                .compact();
    }
}

How I'm doing my tests:

 @Test
        void shouldReturnAllUsers() throws Exception {
                String token = TokenForTest.generateTokenForTest();
                this.mockMvc.perform(get("/api/v1/user")
                        .header("Authorization", token))
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
                        .andExpect(status().isOk()).andDo(print())
                        .andExpect(jsonPath("$.[*].id").exists())
                        .andExpect(jsonPath("$.[*].name").exists())
                        .andExpect(jsonPath("$.[*].email").exists())
                        .andExpect(jsonPath("$.[*].password").exists())
        }

Solution

  • During tests, it is better just use values from src/test/resources/application-test.properties:

    secret=test secret
    

    Please notice the filename, has this "test" suffix. It is there because Spring Boot runs tests in the "test" profile by default. This properties file is a place to define your test configuration. Choose good example values that will be used in your tests. Do not inject example values for tests from the environment, as doing so will make your tests fail in one environment and pass in another.

    To configure application properties for normal run use file src/main/resources/application.properties:

    secret=${SECRET}
    

    This way you can inject the environment variables to application properties using ${ENVIRONMENT_VARIABLE_NAME} syntax. Unlike during the test run where you want to stick to concrete example values, during the normal run of your application, is better to have your application properties to be injected from environment variables. Now if you start your application like this:

    export SECRET=secretFromEnv
    mvn spring-boot:run
    

    it will start with the application property "secret" having the value "secretFromEnv". Just like you had it like secret=secretFromEnv, but it can be set differently in other environments (eg.: staging, production).

    To use the application property value in your spring boot code, as mentioned in the question, @Value annotation can be used:

    @Service
    public class MyService {
      @Value("${secret}")
      private String secret;
    
      public String getSecret() { 
        return secret;
      }
    }
    

    And the test for this service can be like this:

    class MyServiceTest extends IntegrationTestRunner {
    
      @Autowired MyService service;
    
      @Test
      public void testSecret() {
        var serviceSecret = service.getSecret();
        assertEquals("test secret", serviceSecret);
      }
    }
    

    This test asserts that the example value from the application config is returned by the service. During the actual run, the value will be injected from the environment variable, say if our environment variable is set:

    export SECRET=secret-from-env

    And the spring boot application is then started in this shell:

    mvn spring-boot:run

    So now say if in your rest controller there is code like this:

    @Autowired MyService service;
    
    @GetMapping(path = "/secret")
    @ResponseBody public String getSecret() {
      return service.getSecret();
    }
    

    Then calling HTTP GET /secret will print "secret-from-env", as this is the value of environment variable $SECRET.

    Doing this way:

    • Allows us to have example application properties for tests;
    • for non-test setup the values are injected from environment variables;