简体   繁体   中英

Instantiate multiple Spring beans based on different property values

Consider a Spring component like:

@Component
public class Calculator {
  @Value("${rate:1.0}")
  private Double rate;

  ...
}

In a test, I'd like to instantiate three beans of this class based on the settings for property rate :

  1. No value set (test: Calculator should use default value 1.0)
  2. Positive value set (test: Calculator should work normally)
  3. Negative value set (test: Calculator should throw an Exception)

It's easy to achieve this by writing three @SpringBootTest s, each with its own @TestPropertySource .

However, the result is rather ugly (the real production code is more complex than the simple example here).

Is there a way to instantiate three instances of the @Component , each based on a given @TestPropertySource ?

I tried doing this in a @Configuration class; something like:

@Configuration
@TestPropertySource(properties = {"rate=2.0"})
@ContextConfiguration(classes = {Calculator.class})
class PositiveRateCalculatorConfig {
  
  @Autowired
  private Calculator calc;

  @Bean
  Calculator positiveRateCalculator() {
      return calc;  
  }
}

But I didn't manage to auto-wire Calculator into the config class in such a way that @TestPropertySource takes effect.

Assuming there is only one Calculator -bean in production and you want to test multiple configuration scenarios.

This is an excellent example of why the usage of @Autowired and @Value on fields is a bad practice. It kills tests..

Use @Value in a constructor

@Component
public class Calculator {

  private final Double rate;

  Caluclator (@Value("${rate:1.0}") Double rate){
  
  }

  ...
}

Now unit tests are easy

public class CalulatorTest {
  
  @Test
  void positiveRate(){
    Calculator calculator = new Calulator(3d);
    ...
  }

  @Test
  void negativeRate(){
    Calculator calculator = new Calulator(-3d);
    ...
  }
  ...
}

TIP: Avoid @Autowire in other beans

Use constructor injection instead

@Service
public class DemoService{

    private final Calculator calculator;

    public DemoService(Calculator calculator) {
        this.calculator = calculator;
    }
}

And test

public DemoTest {

  DemoService service = new DemoService(new Calculator(2d));
  
  @Test
  void test(){
   ....
  } 

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM