Solución:
Puedo pensar en al menos cuatro enfoques diferentes para su problema. Todo con sus propias ventajas y desventajas.
Enfoque 1: ReflectionTestUtils
Tu estas usando @Value
anotación en una propiedad de instancia privada (por favor, ¡no lo hagas más!). Por tanto, no puedes cambiar acme.fileRepository.basePath
sobre la marcha sin reflejo.
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;
}
}
}
Cambiando basePath
después de cada prueba con ReflectionTestUtils.setField
. Debido a que estamos usando TestExecutionListener de Spring, que se inicializa antes de que se inicialicen las reglas de Junit, nos vemos obligados a administrar la carpeta temporal en beforeTestExecution
y 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();
}
}
}
Enfoque 2: propiedades de configuración
Introduzca una clase de propiedades de configuración para la configuración de su aplicación. Le brinda seguridad de tipografía de forma gratuita y ya no confiamos en la reflexión.
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;
}
}
}
Debido a que estamos usando el TestExecutionListener de Spring, que se inicializa antes de que se inicialicen las reglas de Junit, nos vemos obligados a administrar la carpeta temporal en beforeTestExecution
y 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();
}
}
}
Enfoque 3: Refactorice su código (mi favorito)
Extraer basePath
en su propia clase y esconderlo detrás de una API. Ahora ya no necesita tocar las propiedades de su aplicación y una carpeta temporal.
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;
}
}
}
Ya no necesitamos ninguna instalación de prueba adicional y podemos usar @Rule
sobre TemporaryFolder
en lugar de.
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());
}
}
Enfoque 4: TestPropertySource
Use la anotación TestPropertySource de Spring para anular las propiedades en una prueba de forma selectiva. Debido a que una anotación de Java no puede tener un valor dinámico, debe decidir de antemano dónde desea crear su directorio y tener en cuenta que su prueba está vinculada a un sistema operativo específico debido al separador de ruta del sistema operativo utilizado.
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());
}
}