В Spring Boot Test, как мне сопоставить временную папку со свойством конфигурации? - PullRequest
0 голосов
/ 08 октября 2019

Я хочу сделать тест самоочистки

В моей ситуации один из компонентов зависит от каталога

public class FileRepositoryManagerImpl implements ....

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

}

Значение определено в application.ymlfile, а в DEV он указывает на каталог под 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");
            }
        }
    }
}

Я думал о манипулировании с Environment, но каким должен быть правильный способ программного ввода свойств в тесте Spring Boot?

1 Ответ

1 голос
/ 26 октября 2019

Я могу придумать как минимум четыре разных подхода к вашей проблеме. Все со своими преимуществами и недостатками.

Подход 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();
        }
    }
}



* 1021. * Подход 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;
        }
    }

}

Поскольку мы используем 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());
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...