Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use @Values fields in @Rule in JUnit?

How can I use @Value spring injected properties in a junit @Rule definition?

Background: I want create a junit test with an embedded inmemory sftp server, using FakeSftpServerRule.

Problem: a @Rule is executed before @Value field gets injected.

@Value("${ftp.port}")
private Integer port;

@Value("${ftp.user}")
private String user;

@Value("${ftp.pass}")
private String pass;

@Rule
public final FakeSftpServerRule sftpServer = new FakeSftpServerRule().setPort(port).addUser(user, pass);
like image 273
membersound Avatar asked Jan 17 '26 07:01

membersound


1 Answers

One solution to tackle your problem is to initialize the Spring context and perform the lookup of the properties yourself, as indicated in my initial comment and by referencing to a sample that ilustrated that concept.

The issue here now is, if you don't take care you might end up initializing the Spring context twice, once with the static definition and once with the regular Spring test setup.

Luckily though, Spring itself comes with an AbstractJUnit4SpringContextTests support class that allows you to reuse an already loaded Spring ApplicationContext.

We can leverage this functionality now to initialize the fake SFTP server but also allow to inject beans into the testcode without the need for manual lookup via context.getBean(...) invocations.

The code below should give you an idea how this can be done:

@ContextConfiguration(classes = SpringTest.Config.class)
public class SpringTest extends AbstractJUnit4SpringContextTests {

  @Configuration
  @PropertySource("classpath:some.properties")
  static class Config {
    @Bean(name = "namedBean")
    String someBean() {
      return "Test";
    }

    @Bean
    UUID uuidGenerator() {
      return UUID.randomUUID();
    }
  }

  static ApplicationContext context;
  static int port;
  static String user;
  static String pass;

  static {
    context = new AnnotationConfigApplicationContext(SpringTest.Config.class);
    Environment env = context.getBean(Environment.class);
    String sPort = env.getProperty("port");
    port = Integer.parseInt(sPort);
    user = env.getProperty("user");
    pass = env.getProperty("pass");
  }

  @Autowired
  private UUID uuidGenerator;

  public SpringTest() {
    // reuse the already initialized Spring application context
    setApplicationContext(context);
  }

  @Rule
  public final FakeSftpServerRule sftpServer = 
          new FakeSftpServerRule().setPort(port).addUser(user, pass);

  @Test
  public void test() {
    String someBean = context.getBean("namedBean", String.class);
    System.out.println(someBean);

    System.out.println(uuidGenearator.toString());
    System.out.println(sftpServer.getPort());
  }
}

A probably more elegant way would be to define something like this on the Spring configuration:

@Configuration
@PropertySource("classpath:some.properties")
@EnableConfigurationProperties(SftpSettings.class)
static class Config {
  ...
}

where SftpSettings is a simple bean class such as

@Validated
@Getter
@Setter
@ConfigurationProperties(prefix="sftp")
public class SftpSettings {
  @NotNull
  private int port;
  @NotNull
  private String user;
  @NotNull
  private String pass;
}

and then perform the lookup on the SftpSettings instead of the Environment:

static {
  context = new AnnotationConfigApplicationContext(SpringTest.Config.class);
  SftpSettings settings = context.getBean(SftpSettings.class);
  port = settings.getPort();
  user = settings.getUser();
  pass = settings.getPass();
}

This way Spring will take care of looking up the values from the property file and on converting these values to the appropriate formats.

like image 194
Roman Vottner Avatar answered Jan 19 '26 21:01

Roman Vottner