3
votes

This is driving me up the wall trying to get spring boot @configurationproperties annotation working. So hoping someone can shed some light on this for me as to what I am doing wrong. I have a spring boot application and it contains a application.properties on the classpath. It has a value in there of

server.contextPath=/test/v1
server.port=8080

spring.profiles.active=dev
vendors=me

I have a application.class which has the spring boot annotation and sits at the top of my package hierarchy

package com.test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableConfigurationProperties
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

I am trying to map the property vendors into a configurationproperties bean as below

package com.test.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@Component
@PropertySource("classpath:application.properties")
@ConfigurationProperties
public class GlobalProperties {

    private String vendors;

    public String getVendors() {
        return vendors;
    }

    public void setVendors(String vendors) {
        this.vendors = vendors;
    }
}

and then call this bean from my rest controller. I know it resolves the property as when i rename it the server fails to start. In the code below the props bean is not getting autowired and is null. //code ommitted for brevity

package com.test.controller;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.test.config.GlobalProperties;

@RestController
@Component
public class MController {


    //TODO should be wired in from properties file 
    @Autowired
    GlobalProperties props;


    private boolean vendorUnknown(String vendor) {
        if(props.getAllowedVendor().equalsIgnoreCase(vendor)) {
            return true;
        }
        return false;
    }

    @RequestMapping(value = "/test/{id}", method = RequestMethod.GET, produces = { "application/json" })
    public ResponseEntity<?> getStatus(
            @PathVariable String id) {
        //@RequestBody Bookmark input
        if(vendorUnknown("me")) {
        System.out.println("found");
    };
        return ResponseEntity.noContent().build();
    }

}

Anybody point me to what i have done wrong please?

UPDATE:

changed the code above to be a more simplistic version with a test class to recreate the issue. See below for my pom.xml and test class

Pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.me.test</groupId>
    <artifactId>test-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- tag::actuator[] -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- end::actuator[] -->
        <!-- tag::tests[] -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- end::tests[] -->
    </dependencies>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <artifactId>maven-failsafe-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

Test class:

package com.test.controller;

import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;


@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @InjectMocks
    MController testController;

    @Before
    public void setup() {

        // this must be called for the @Mock annotations above to be processed
        // and for the mock service to be injected into the controller under
        // test.
        MockitoAnnotations.initMocks(this);

        this.mockMvc = MockMvcBuilders.standaloneSetup(testController).build();
    }

    @Test
    public void testPropertiesMsg() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/test/1").accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk());
    }
}
2

2 Answers

2
votes

Add @EnableConfigurationProperties to your Application class to enable scanning ConfigurationProperties beans.

2
votes
  1. Remove @PropertySource. You can keep @Component, or if you don't, specify @EnableConfigurationProperties(GlobalProperties.class).
  2. Don't need @Component on testController. But I think your issue is where you're calling vendorUnknown method from. It's not shown in your code. If calling from the constructor, then the bean initialization has not completed yet, and GlobalProperties props is indeed null.

Edit:

Based on OP's edit, here's a fully working solution.

DemoApplication.java:

@SpringBootApplication
@EnableConfigurationProperties
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

DemoController.java:

@RestController
public class DemoController {
    @Autowired
    private DemoProperties props;

    @GetMapping(value = "/", produces = APPLICATION_JSON_VALUE)
    public ResponseEntity<?> getVendor() {
        return ResponseEntity.ok(props.getVendors());
    }
}

DemoProperties.java:

@ConfigurationProperties
@Component
public class DemoProperties {
    private String vendors;

    public String getVendors() {
        return vendors;
    }

    public void setVendors(String vendors) {
        this.vendors = vendors;
    }
}

application.properties:

vendors=me

DemoControllerTest.java:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DemoControllerTest {
    @Autowired
    TestRestTemplate restTemplate;

    @Test
    public void testGenVendor() throws Exception {
        String vendor = restTemplate.getForObject("/", String.class);

        assertThat(vendor).isEqualTo("me");
    }
}

Problems with OP's code:

  1. MockMvc doesn't use the main class, so EnableConfigurationPropertiesis not processed. MockMvc or WebMvcTest is designed to test the web layer (duh!), not the whole app. Using either of those. the properties bean should be set by the test.

  2. InjectMocks fails silently. See Why You Should Not Use InjectMocks Annotation to Autowire Fields.