How do I parameterize property values for spring boot test
Asked Answered
A

3

8

I am working on an aggregation framework using spring boot, which can be used to aggregate multiple kinds of I/O operations' results. Additionally, it has support for both non-blocking (reactive) & blocking aggregation layer, which is controlled by a property, which is in-turn used for conditional beans. For integration testing, I have been using this setup:

abstract class HttpUpstreamTests {
    // Setup & test logic
}

@SpringBootTest(properties = {
        "aggregation.reactive.enabled=false",
})
class BlockingAggregationIT extends HttpUpstreamTests {}

@SpringBootTest(properties = {
        "aggregation.reactive.enabled=true",
})
class NonBlockingAggregationIT extends HttpUpstreamTests {}

Now I have to add a couple more base classes:

abstract class MongodbUpstreamTests {
    // Setup & test logic
}
abstract class SqlUpstreamTests {
    // Setup & test logic
}

But of course, I can't make the IT classes extend from multiple abstract classes. One way would be to make all the upstrteam tests parameterized tests, but I can't seem to find a way to use that approach with different values of the property in @SpringBootTest(properties="aggregation.reactive.enabled=false"). Is there a way to use different properties as a parameter for parameterized tests?

Antoninus answered 15/3, 2022 at 10:24 Comment(0)
T
1

Rather than using class hierarchy, use composition. Restructure your HttpUpstreamTests, MongodbUpstreamTests and SqlUpstreamTests classes to act as self-contained concrete helper classes that you can instantiate and call methods on them to do the setup and tests you need.

This will help with modularity and probably simplify how each of those classes works.

For example, it might look something like this:

class HttpUpstreamTests {
  public HttpUpstreamTests(some params...)...

  public void setup()...

  public void testSomething(...)
}

class BlockingAggregationIT {

  private HttpUpstreamTests httpUpstreamTests;

  @Before
  public void setup(){
    httpUpstreamTests = new HttpUpstreamTests(...);
    httpUpstreamTests.setup();
  }

  @Test
  public void testOne(){
    // ...
    httpUpstreamTests.testSomething();
    // ...
  }
}

I'm guessing you have a controller, or some other class that you are trying to test here. You could pass a reference to that controller as a parameter to the constructor, setup method or even the testSomething method. Likewise for any other mocks and things that might need to be shared between your test class and these helper classes.

Toothpaste answered 10/5, 2024 at 15:47 Comment(2)
Essentially I want to parameterize all the tests on different values of an application property. Using this approach I'll have to create two classes which will have exactly the same methods duplicated across them.Antoninus
You could also try making the base classes to be interfaces and all the methods to be "default" methods on the interfaces. But I'm not sure that's a great pattern and you will likely run into limitations with what's allowed, since interfaces cannot have instance variables.Toothpaste
R
0

using spring boot profiles

in order to create different beans per different configuration i'd advice you use different configuration profiles.

for example:

@Profile("REACTIVE")
@Configuration // replace with @TestConfiguration if you need to support auto-detection of @SpringBootConfiguration
public class ReactiveConf {

  @Bean
  //your reactive beans ..
}
@Profile("NON_REACTIVE")
@Configuration
public class NonReactiveConf {

  @Bean
  //your non-reactive beans ..
}

and same for other configurations:

@Profile("MONGO")
@Configuration
public class MongoConf {

  @Bean
  //your mongo related beans ..
}

when running IT you can decide which profiles are now active (can choose more than one. for example SPRING_PROFILES_ACTIVE=REACTIVE,MONGO and run the same IT with 4 different combinations.

alternatively you can explicitly create 4 different tests like this one

@Import({ReactiveConf.class, MongoConf.class})
@SpringBootTest
class ReactiveMongoIT extends AbstractIT {}

keep with current approach

if you wish to stay with closer to your current solution, one approach could be to toggle non/reactive by introducing a environment variable.

this way you could run the same IT test twice - once with reactive settings and once as non reactive. without the need to create dedicated classes BlockingAggregationIT and Non BlockingAggregationIT

you can run the following test with env var -DIS_REACTIVE=false and once without (-DIS_REACTIVE=true is default in the following example)

@SpringBootTest(properties = "aggregation.reactive.enabled=${IS_REACTIVE:true}") 
public class ApplicationIT {

    @Value("${aggregation.reactive.enabled}")
    boolean isReactive;

    @Test
    void contextLoads() {
        System.out.println("isReactive:" + isReactive);
    }
}
Respire answered 12/5, 2024 at 16:41 Comment(4)
run the same IT with 4 different combinations How do I accomplish this using a parameterized test?Antoninus
for example (using maven) mvn test -Dtest=path.to.ApplicationIT -DSPRING_PROFILES_ACTIVE="REACTIVE,MONGO" and execute again with -DSPRING_PROFILES_ACTIVE="REACTIVE,MYSQL".Respire
Got it, the requirement I have is that a single run of mvn test should be the only thing required, as is the case with normal parameterized tests.Antoninus
AFAIK @SpringBootTest has it's life cycle. Standard @ParameterizedTest will load after the context of your test app is loaded. So no proper way to do it. (Of course you can hack it, but i wouldn't recommend to). In such case follow the @Import approachRespire
I
0

Is there a way to use different properties as a parameter for parameterized tests?

If I understand you correctly you want to change the value of a certain property by using that property value as a parameter for parameterized integration tests. You can try Spring's TestPropertySourceUtils:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.core.env.ConfigurableEnvironment;

import java.util.stream.Stream;

@ExtendWith(SpringExtension.class)
@SpringBootTest
public class CustomUpstreamIT {

  @Autowired
  private ConfigurableEnvironment environment;

  static Stream<String> propertyValues() {
    return Stream.of("true", "false");
  }

  @ParameterizedTest
  @MethodSource("propertyValues")
  public void testIntegration(String input) {
    // Change the property value programmatically
    TestPropertySourceUtils.addInlinedPropertiesToEnvironment(environment, "aggregation.reactive.enabled=" + input);

    // Retrieve the updated property value
    String yourProperty = environment.getProperty("aggregation.reactive.enabled");

    // Use yourProperty and input to test your integration
    System.out.println("Testing with property: " + yourProperty + ", input: " + input);
    // Your test logic goes here ...
  }
}

If you want to test multiple configurations of properties you can change the propertyValue method to something like:

    static Stream<Arguments> parameterCombinations() {
        return Stream.of(
            Arguments.of("value1", "paramA"),
            Arguments.of("value2", "paramB"),
            // Add more combinations as needed
        );
    }

and change the test method signature as needed.

Indevout answered 15/5, 2024 at 9:21 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.