如何在JUnit的@Rule中使用@Values字段?

时间:2019-06-19 11:57:51

标签: java spring junit spring-boot-test

如何在junit @Value定义中使用@Rule弹簧注入属性?

背景:我想使用FakeSftpServerRule用嵌入式内存sftp服务器创建一个junit测试。

问题:在插入@Rule字段之前执行了@Value

@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);

2 个答案:

答案 0 :(得分:1)

解决您的问题的一种方法是初始化Spring上下文并自己执行属性查找,如我的初始评论和referencing to a sample所阐明的那样。

现在的问题是,如果不注意,最终可能会两次初始化Spring上下文,一次是使用静态定义,一次是使用常规的Spring测试设置。

幸运的是,Spring本身带有AbstractJUnit4SpringContextTests支持类,允许您重用已经加载的Spring ApplicationContext。

我们现在可以利用此功能来初始化伪造的SFTP服务器,还可以将bean注入测试代码中,而无需通过context.getBean(...)调用进行手动查找。

下面的代码应使您了解如何完成此操作:

@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());
  }
}

一种可能更优雅的方法是在Spring配置中定义如下内容:

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

其中SftpSettings是一个简单的bean类,例如

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

,然后在SftpSettings而非Environment上执行查找:

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

这样,Spring将负责从属性文件中查找值,并将这些值转换为适当的格式。

答案 1 :(得分:0)

您可以在方法上使用 @Rule 注释,该方法在值注入后执行。至少 SpringJUnit4ClassRunner 的情况似乎是这样。

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

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

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

@Rule
public FakeSftpServerRule getFakeSftpServerRule() {
    return new FakeSftpServerRule().setPort(port).addUser(user, pass);
}

请注意,如果您的规则同时实现了 TestRuleMethodRule 接口,则该方法可能会被选取并执行两次,这可能会导致一些问题。为避免这种情况,请将方法的返回类型更改为 TestRule

@Rule
public TestRule getFakeSftpServerRule() {
    return new FakeSftpServerRule().setPort(port).addUser(user, pass);
}