0

I'm writing automated test using TestNG for the REST API of my application. The application has a RestController which contains an @Autowired service class. When the REST endpoint is called with a HTTP GET request, the service looks into a storage directory for XML files, transforms their contents into objects and stores them in a database. The important thing for my question is that the path to the storage directory is stored in /src/main/resources/application.yml (source.storage) and imported via a @Value annotation.

Now, I have the source.storage property also in src/test/resources/application.yml pointing to a different directory within src/test, where I store my testing XML files, and import them to my test class with a @Value annotation again. My test calls the REST endpoint with a HTTP GET. However, it seems that the service still draws the source.storage property the main application.yml, while I would like that value overriden by the one in test application.yml file. In other words, the service tries to import XML files from the application storage directory, rather than from my testing storage.

@ActiveProfiles and @TestPropertySource do not seem to work for me. Scanning the main application.yml for its storage property is not an option, as in the end the application.yml will be drawn from a Spring Cloud Config, and I would not know where the main application.yml would be located.

Is there a way with which I could make the @Autowired service draw the source.storage property from the test application.yml, rather from the main one?

Any advice would be appreciated.

Thanks, Petr

Krishnan Mahadevan
  • 14,121
  • 6
  • 34
  • 66

2 Answers2

1

Well, it really depends on what you're trying to build, if it is some sort of unit test of the controller or more likely an integration test. Both approaches are explained in this tutorial.

If you're trying to write integration test, which seems a bit more likely from your question, then @ActiveProfiles or @TestPropertySource should work for you. I would suggest to use profiles, in growing application with a lot of properties it is a bit more convenient to just replace some of the properties for the testing. Below is setup which worked for me when writing integration tests for controller endpoints:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class AreaControllerTest {

    @Autowired
    TestRestTemplate rest;

    @MockBean
    private JobExecutor jobExecutor;


    @Test
    public void test01_List() {
        //
    }

    @Test
    public void test02_Get() {
        //
    }
    
    // ...
}

There are several important things.

  • The testing properties are in src/test/resources/application-test.properties and merges with the ones in application.properties as the @ActiveProfiles("test") annotation suggests.
  • Essential is also @RunWith(SpringRunner.class) which is JUnit specific, for TestNG alternative please refer to this SO question.
  • Finally the @SpringBootTest annotation will start the whole application context.
  • @FixMethodOrder and @DirtiesContext are further setup of the testing case and are not really necessary.
  • Notice also the @MockBean annotation, in this case we did not wanted to use real-life implementation of JobExecutor, so we replaced it with mock.

If you want to write unit test where you want to just check the logic of controller and service on their own, then you have to have two test classes, each testing respective classes. Testing service should be standard unit test, testing controller is a bit trickier and is probably more inclined to partial integration test. If this is your case I would recommend to use MockMvc approach explained in the above mentioned tutorial. Small snippet from there:

@RunWith(SpringRunner.class)
@WebMvcTest(GreetingController.class)
public class WebMockTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private GreetingService service;

    @Test
    public void greetingShouldReturnMessageFromService() throws Exception {
        when(service.greet()).thenReturn("Hello Mock");
        this.mockMvc.perform(get("/greeting")).andDo(print()).andExpect(status().isOk())
                .andExpect(content().string(containsString("Hello Mock")));
    }
}

Notice the @MockBean annotation which mocks service where you can specify your own behaviour of mock. This point is critical, because this sort of test does not load whole application context, but only MVC context, so the services are not available. Again as in the integration test the @RunWith(SpringRunner.class) annotation is essential. Finally @WebMvcTest(GreetingController.class) starts only MVC context of the GreetingController class and not the whole application.

Neloop
  • 139
  • 7
0

You can try supplying the property directly to the spring boot test.

    @SpringBootTest(properties= {"source.storage=someValue"})

Regarding the application picking up the wrong property source, You should also check if your application is being built properly.

nakul shukla
  • 138
  • 1
  • 8