spring-boot - JUnit - 在Spring Boot测试中,如何将临时文件夹映射到配置属性?

我的情况中,我有一个组件依赖于一个目录,


public class FileRepositoryManagerImpl implements ....



 @Value("${acme.fileRepository.basePath}")


 private File basePath;



}



值在application.yml文件中定义,并且在开发中指向build下的一个目录。

这不是最坏的做法,因为gradle clean最终会清理测试所产生的混乱。

但是实际上,我想实现的是确保每个测试都在一个隔离的临时目录中运行,该目录在执行后会被清除。

我知道JUnit有一个临时目录的工具,但是一旦我在JUnit 4的范围内定义了该目录,我该如何告诉Spring使用该临时目录?

我尝试了内部类不成功:


@RunWith(SpringRunner.class)


@SpringBootTest(classes = { SecurityBeanOverrideConfiguration.class, App.class })


@EnableConfigurationProperties


public abstract class AbstractFileRepositoryManagerIntTests {



 private final static TemporaryFolder temporaryFolder = new TemporaryFolder();



 @ClassRule


 public static TemporaryFolder getTemporaryFolder()


 {


 return temporaryFolder;


 }



 @ConfigurationProperties(prefix ="acme")


 static class Configuration


 {



 public FileRepository getFileRepository()


 {


 return new FileRepository();


 }



 static class FileRepository


 {



 public File basePath() throws Exception


 {


 return temporaryFolder.newFolder("fileRepositoryBaseDir");


 }


 }


 }


}



时间:

我可以想到至少四个不同的方法来解决你的问题,都有自己的优点和缺点。

方法1:ReflectionTestUtils

你在私有实例属性(请不要再这样了)上使用了@Value注释,因此,你不能在没有反射的情况下动态改变acme.fileRepository.basePath


package demo;



import org.springframework.beans.factory.annotation.Value;


import org.springframework.boot.SpringApplication;


import org.springframework.boot.autoconfigure.SpringBootApplication;


import org.springframework.stereotype.Component;



import java.io.File;



@SpringBootApplication


public class FileRepositoryApp {



 public static void main(String[] args) {


 SpringApplication.run(FileRepositoryApp.class, args);


 }



 @Component


 public class FileRepository {



 @Value("${acme.fileRepository.basePath}")


 private File basePath;



 public File getBasePath() {


 return basePath;


 }


 }


}



使用ReflectionTestUtils.setField在每次测试后,更改basePath ,因为使用testExecutionListener的,在初始化Junit规则之前,我们被迫管理和afterTestMethod的临时文件夹。


package demo;



import org.junit.Test;


import org.junit.rules.TemporaryFolder;


import org.junit.runner.RunWith;


import org.springframework.beans.factory.annotation.Autowired;


import org.springframework.boot.test.context.SpringBootTest;


import org.springframework.test.context.TestContext;


import org.springframework.test.context.TestExecutionListener;


import org.springframework.test.context.TestExecutionListeners;


import org.springframework.test.context.junit4.SpringRunner;


import org.springframework.test.util.ReflectionTestUtils;



import java.io.IOException;



import static junit.framework.TestCase.assertEquals;


import static org.springframework.test.context.TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS;



@RunWith(SpringRunner.class)


@SpringBootTest(classes = FileRepositoryApp.class)


@TestExecutionListeners(listeners = FileRepositoryAppTest.SetBasePath.class, mergeMode = MERGE_WITH_DEFAULTS)


public class FileRepositoryAppTest {



 private static TemporaryFolder temporaryFolder = new TemporaryFolder();



 @Autowired


 private FileRepositoryApp.FileRepository fileRepository;



 @Test


 public void method() {


 System.out.println(temporaryFolder.getRoot().getAbsolutePath());


 System.out.println(fileRepository.getBasePath());


 assertEquals(temporaryFolder.getRoot(), fileRepository.getBasePath());


 }



 @Test


 public void method1() {


 System.out.println(temporaryFolder.getRoot().getAbsolutePath());


 System.out.println(fileRepository.getBasePath());


 assertEquals(temporaryFolder.getRoot(), fileRepository.getBasePath());


 }



 static class SetBasePath implements TestExecutionListener {



 @Override


 public void beforeTestExecution(TestContext testContext) throws IOException {


 temporaryFolder.create();


 if (testContext.hasApplicationContext()) {


 FileRepositoryApp.FileRepository bean = testContext.getApplicationContext().getBean(FileRepositoryApp.FileRepository.class);


 ReflectionTestUtils.setField(bean,"basePath", temporaryFolder.getRoot());


 }


 }



 @Override


 public void afterTestMethod(TestContext testContext) {


 temporaryFolder.delete();


 }


 }


}




方法2:配置属性

为应用程序配置引入一个配置属性类,可以给你类型安全,而不再依赖反射。


package demo;



import org.springframework.boot.SpringApplication;


import org.springframework.boot.autoconfigure.SpringBootApplication;


import org.springframework.boot.context.properties.ConfigurationProperties;


import org.springframework.stereotype.Component;



import java.io.File;



@SpringBootApplication


public class FileRepositoryWithPropertiesApp {



 public static void main(String[] args) {


 SpringApplication.run(FileRepositoryWithPropertiesApp.class, args);


 }



 @Component


 public class FileRepository {



 private final FileRepositoryProperties fileRepositoryProperties;



 public FileRepository(FileRepositoryProperties fileRepositoryProperties) {


 this.fileRepositoryProperties = fileRepositoryProperties;


 }



 public File getBasePath() {


 return fileRepositoryProperties.getBasePath();


 }


 }



 @Component


 @ConfigurationProperties(prefix ="acme.file-repository")


 public class FileRepositoryProperties {



 private File basePath;



 public File getBasePath() {


 return basePath;


 }



 public void setBasePath(File basePath) {


 this.basePath = basePath;


 }


 }



}



因为使用testExecutionListener,在初始化Junit规则之前,我们被迫管理和afterTestMethod的临时文件夹。


package demo;



import org.junit.Test;


import org.junit.rules.TemporaryFolder;


import org.junit.runner.RunWith;


import org.springframework.beans.factory.annotation.Autowired;


import org.springframework.boot.test.context.SpringBootTest;


import org.springframework.test.context.TestContext;


import org.springframework.test.context.TestExecutionListener;


import org.springframework.test.context.TestExecutionListeners;


import org.springframework.test.context.junit4.SpringRunner;



import java.io.IOException;



import static junit.framework.TestCase.assertEquals;


import static org.springframework.test.context.TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS;



@RunWith(SpringRunner.class)


@SpringBootTest(classes = FileRepositoryWithPropertiesApp.class)


@TestExecutionListeners(listeners = FileRepositoryWithPropertiesTest.SetBasePath.class, mergeMode = MERGE_WITH_DEFAULTS)


public class FileRepositoryWithPropertiesTest {



 private static TemporaryFolder temporaryFolder = new TemporaryFolder();



 @Autowired


 private FileRepositoryWithPropertiesApp.FileRepository bean;



 @Test


 public void method() {


 System.out.println(temporaryFolder.getRoot().getAbsolutePath());


 System.out.println(bean.getBasePath());


 assertEquals(temporaryFolder.getRoot(), bean.getBasePath());


 }



 @Test


 public void method1() {


 System.out.println(temporaryFolder.getRoot().getAbsolutePath());


 System.out.println(bean.getBasePath());


 assertEquals(temporaryFolder.getRoot(), bean.getBasePath());


 }



 static class SetBasePath implements TestExecutionListener {



 @Override


 public void beforeTestExecution(TestContext testContext) throws IOException {


 temporaryFolder.create();


 if (testContext.hasApplicationContext()) {


 FileRepositoryWithPropertiesApp.FileRepositoryProperties bean = testContext.getApplicationContext().getBean(FileRepositoryWithPropertiesApp.FileRepositoryProperties.class);


 bean.setBasePath(temporaryFolder.getRoot());


 }


 }



 @Override


 public void afterTestMethod(TestContext testContext) {


 temporaryFolder.delete();


 }


 }


}



方法3:重构代码(我最喜欢)

basePath提取到自己的类中并将它隐藏在api后面,现在你不再需要使用你的应用程序属性和临时文件夹了。


package demo;



import org.springframework.boot.SpringApplication;


import org.springframework.boot.autoconfigure.SpringBootApplication;


import org.springframework.boot.context.properties.ConfigurationProperties;


import org.springframework.stereotype.Component;



import java.io.File;



@SpringBootApplication


public class FileRepositoryWithAbstractionApp {



 public static void main(String[] args) {


 SpringApplication.run(FileRepositoryWithAbstractionApp.class, args);


 }



 @Component


 public class FileRepository {



 private final FileRepositorySource fileRepositorySource;



 public FileRepository(FileRepositorySource fileRepositorySource) {


 this.fileRepositorySource = fileRepositorySource;


 }



 public File getBasePath() {


 return fileRepositorySource.getBasePath();


 }


 }



 @Component


 public class FileRepositorySource {



 private final FileRepositoryProperties fileRepositoryProperties;



 public FileRepositorySource(FileRepositoryProperties fileRepositoryProperties) {


 this.fileRepositoryProperties = fileRepositoryProperties;


 }



 public File getBasePath() {


 return fileRepositoryProperties.getBasePath();


 }


 }



 @Component


 @ConfigurationProperties(prefix ="acme.file-repository")


 public class FileRepositoryProperties {



 private File basePath;



 public File getBasePath() {


 return basePath;


 }



 public void setBasePath(File basePath) {


 this.basePath = basePath;


 }


 }


}



我们不再需要额外的测试工具,我们可以在TemporaryFolder上使用@Rule


package demo;



import org.junit.Before;


import org.junit.Rule;


import org.junit.Test;


import org.junit.rules.TemporaryFolder;


import org.junit.runner.RunWith;


import org.springframework.beans.factory.annotation.Autowired;


import org.springframework.boot.test.context.SpringBootTest;


import org.springframework.boot.test.mock.mockito.MockBean;


import org.springframework.test.context.junit4.SpringRunner;



import static junit.framework.TestCase.assertEquals;


import static org.mockito.Mockito.when;



@RunWith(SpringRunner.class)


@SpringBootTest(classes = FileRepositoryWithAbstractionApp.class)


public class FileRepositoryWithAbstractionTest {



 @Rule


 public TemporaryFolder temporaryFolder = new TemporaryFolder();



 @MockBean


 private FileRepositoryWithAbstractionApp.FileRepositorySource fileRepositorySource;



 @Autowired


 private FileRepositoryWithAbstractionApp.FileRepository bean;



 @Before


 public void setUp() {


 when(fileRepositorySource.getBasePath()).thenReturn(temporaryFolder.getRoot());


 }



 @Test


 public void method() {


 System.out.println(temporaryFolder.getRoot().getAbsolutePath());


 System.out.println(bean.getBasePath());


 assertEquals(temporaryFolder.getRoot(), bean.getBasePath());


 }



 @Test


 public void method1() {


 System.out.println(temporaryFolder.getRoot().getAbsolutePath());


 System.out.println(bean.getBasePath());


 assertEquals(temporaryFolder.getRoot(), bean.getBasePath());


 }



}




方法4:TestPropertySource

使用spring注释的testPropertySource有选择地重写测试中的属性,由于Java注释不能有动态值,因此你需要事先确定要在哪里创建目录,并要记住,由于使用了os路径分隔符,因此测试已绑定到特定的操作系统。


package demo;



import org.junit.After;


import org.junit.Before;


import org.junit.Test;


import org.junit.runner.RunWith;


import org.springframework.beans.factory.annotation.Autowired;


import org.springframework.boot.test.context.SpringBootTest;


import org.springframework.test.context.TestPropertySource;


import org.springframework.test.context.junit4.SpringRunner;



import java.io.IOException;


import java.nio.file.Files;


import java.nio.file.Path;


import java.nio.file.Paths;



import static demo.FileRepositoryTestPropertySourceTest.BASE_PATH;



@RunWith(SpringRunner.class)


@SpringBootTest(classes = FileRepositoryApp.class)


@TestPropertySource(properties ="acme.fileRepository.basePath=" + BASE_PATH)


public class FileRepositoryTestPropertySourceTest {



 static final String BASE_PATH ="/tmp/junit-base-path";



 private Path basePath = Paths.get(BASE_PATH);;



 @Autowired


 private FileRepositoryApp.FileRepository fileRepository;



 @Before


 public void setUp() throws IOException {


 Files.deleteIfExists(basePath);


 Files.createDirectories(basePath);


 }



 @After


 public void after() throws IOException {


 Files.deleteIfExists(basePath);


 }



 @Test


 public void method() {


 System.out.println(fileRepository.getBasePath());


 }


}



...