我要进行自我清洁测试
在我的情况下,我的组件之一取决于目录
public class FileRepositoryManagerImpl implements ....
@Value("${acme.fileRepository.basePath}")
private File basePath;
}
该值在application.yml
文件中定义,在DEV中它指向build
下的目录。
这不是最坏的主意,因为gradle clean
最终将清理测试创建的混乱。
但是,实际上,我想在这里实现的是,确保每个测试都在一个隔离的临时目录中运行,该目录在执行后会被清除。
我知道JUnit有一个用于临时目录的工具。但是一旦我在JUnit 4的范围内定义了该目录,如何告诉Spring使用该临时目录?
我tried内部类未成功:
@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");
}
}
}
}
我正在考虑使用Environment
进行修补,但是在Spring Boot测试中以编程方式注入属性的正确方法应该是什么?
我可以想到至少四种不同的方法来解决您的问题。都有各自的优点和缺点。
方法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;
}
}
}
每次用basePath
进行测试后更改ReflectionTestUtils.setField
。因为我们使用的是Spring的TestExecutionListener,它是在初始化Junit规则之前初始化的,所以我们不得不管理beforeTestExecution
和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:配置属性
为您的应用程序配置引入配置属性类。它免费为您提供type safety,我们不再依赖反射。
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; } } }
由于我们使用的是Spring的TestExecutionListener,它是在初始化Junit规则之前初始化的,因此我们被迫管理
beforeTestExecution
和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; } // TODO for the sake of brevity no real api here 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; } } }
我们不再需要任何其他测试工具,可以在
@Rule
上使用TemporaryFolder
。
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());
}
}