[英]Testing Spring Integration Flow
實際上,我正在創建一個用於運行 Spring 與 Kubernetes 集成的 POC,為此我創建了一個集成流,該流讀取 XML 文件並將其移動到 Processed Dir 如果它是一個有效的 xml 文件,否則將其移動到 Error Dir
package com.stackoverflow.questions.config;
import static java.util.Arrays.asList;
import com.stackoverflow.questions.dto.WriteResult;
import com.stackoverflow.questions.handler.FileReaderHandler;
import com.stackoverflow.questions.handler.StudentErrorHandler;
import com.stackoverflow.questions.handler.StudentWriterHandler;
import com.stackoverflow.questions.service.DirectoryManagerService;
import com.stackoverflow.questions.transformer.FileToStudentTransformer;
import java.io.File;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.MessageChannels;
import org.springframework.integration.dsl.Pollers;
import org.springframework.integration.file.DirectoryScanner;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.file.RecursiveDirectoryScanner;
import org.springframework.integration.file.filters.AcceptOnceFileListFilter;
import org.springframework.integration.file.filters.CompositeFileListFilter;
import org.springframework.integration.file.filters.RegexPatternFileListFilter;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.messaging.MessageChannel;
@Configuration
@EnableIntegration
@RequiredArgsConstructor
public class MainIntegrationFlow {
@Value("${regex.filename.pattern}")
private String regexFileNamePattern;
@Value("${root.file.dir}")
private String rootFileDir;
@Value("${default.polling.rate}")
private Long defaultPollingRate;
private final DirectoryManagerService directoryManagerService;
private final StudentErrorHandler studentErrorHandler;
private final FileReaderHandler fileReaderHandler;
private final StudentWriterHandler studentWriterHandler;
private final FileToStudentTransformer fileToStudentTransformer;
@Bean("mainStudentIntegrationFlow")
public IntegrationFlow mainStudentIntegrationFlow(
@Qualifier("mainFileReadingSourceMessage") MessageSource<File> mainFileReadingSourceMessage,
@Qualifier("fileReaderChannel") MessageChannel fileReaderChannel) {
return IntegrationFlows.from(mainFileReadingSourceMessage)
.channel(fileReaderChannel)
.handle(fileReaderHandler)
.transform(fileToStudentTransformer)
.handle(studentWriterHandler)
.<WriteResult, Boolean>route(WriteResult::isWriten,
mapping -> mapping
.subFlowMapping(true, moveToProcessedDirFlow())
.subFlowMapping(false, moveToErrorDirFlow()))
.get();
}
public IntegrationFlow moveToProcessedDirFlow() {
return flow -> flow.handle(message ->
directoryManagerService
.moveToProcessedDir(((WriteResult) message.getPayload()).getFilename()));
}
public IntegrationFlow moveToErrorDirFlow() {
return flow -> flow.channel("studentErrorChannel")
.handle(message ->
directoryManagerService
.moveToErrorDir(((WriteResult) message.getPayload()).getFilename()));
}
@Bean(name = "errorHandlerMainFlow")
public IntegrationFlow errorHandlerMainFlow() {
return IntegrationFlows.from("errorChannel")
.handle(studentErrorHandler)
.get();
}
@Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata mainPollerMetadata() {
return Pollers.fixedRate(defaultPollingRate, TimeUnit.SECONDS)
.maxMessagesPerPoll(0)
.get();
}
@Bean(name = "fileReaderChannel")
public MessageChannel fileReaderChannel() {
return MessageChannels.queue("fileReaderChannel").get();
}
@Bean("mainDirectoryScanner")
public DirectoryScanner mainDirectoryScanner() {
DirectoryScanner recursiveDirectoryScanner = new RecursiveDirectoryScanner();
CompositeFileListFilter<File> compositeFileListFilter = new CompositeFileListFilter<>(
asList(new AcceptOnceFileListFilter<>(),
new RegexPatternFileListFilter(regexFileNamePattern)));
recursiveDirectoryScanner.setFilter(compositeFileListFilter);
return recursiveDirectoryScanner;
}
@Bean("mainFileReadingSourceMessage")
public MessageSource<File> mainFileReadingSourceMessage(
@Qualifier("mainDirectoryScanner") DirectoryScanner mainDirectoryScanner) {
FileReadingMessageSource fileReadingMessageSource = new FileReadingMessageSource();
fileReadingMessageSource.setDirectory(new File(rootFileDir));
fileReadingMessageSource.setScanner(mainDirectoryScanner);
return fileReadingMessageSource;
}
}
我正在嘗試測試整個流程,為此我創建了一個測試類:
@SpringBootTest
@SpringIntegrationTest(noAutoStartup = "fileReadingEndpoint")
public class MainFlowIntegratoinTests {
@Autowired
private MockIntegrationContext mockIntegrationContext;
@Autowired
private SourcePollingChannelAdapter fileReadingEndpoint;
@Test
public void readingInvalidFileAndMoveItToErrorDir() throws IOException {
File file = new ClassPathResource("valid01-student-01.xml").getFile();
MessageSource<File> mockInvalidStudentFile = () -> MessageBuilder.withPayload(file).build();
mockIntegrationContext.substituteMessageSourceFor("fileReadingEndpoint", mockInvalidStudentFile);
// start the file adapter manually
fileReadingEndpoint.start();
}
}
我正在測試我的集成流程,但不知何故測試沒有到達寫入器端點,我可以看到來自讀取器和轉換器端點的日志,但不能從寫入器看到日志。
我試圖閱讀文檔 - https://docs.spring.io/spring-integration/reference/html/testing.html - 但我無法弄清楚。
請給我們一個示例或更多有關如何測試整個集成流程的詳細信息。
工作測試:
package com.stackoverflow.questions;
import static org.apache.commons.io.FileUtils.forceDelete;
import static org.awaitility.Awaitility.await;
import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD;
import com.stackoverflow.questions.service.DirectoryManagerService;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.util.ReflectionTestUtils;
@SpringBootTest
@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD)
public class MainFlowIntegrationTests {
private static final String MOCK_FILE_DIR = "intFiles/";
private static final String VALID_XML_MOCK_FILE = "valid01-student-01.xml";
private static final String INVALID_XML_MOCK_FILE = "invalid02-student-02.xml";
@Autowired
private MessageChannel fileReaderChannel;
@Autowired
private DirectoryManagerService directoryManagerService;
private File queueDir;
private File processed;
private File error;
@BeforeEach
public void setup() throws IOException {
createRequiredDirectories();
moveFilesToQueueDir();
injectProperties();
}
@AfterEach
public void tearDown() throws IOException {
deleteRequiredDirectories();
}
@Test
public void readingFileAndMoveItToProcessedDir() throws IOException, InterruptedException {
// When: the fileReaderChannel receives a valid XML file
fileReaderChannel
.send(MessageBuilder.withPayload(new File(queueDir, VALID_XML_MOCK_FILE)).build());
// Then: the valid XML file should be sent to the processedDir
await().until(() -> processed.list().length == 1);
}
@Test
public void readingInvalidFileAndMoveItToErrorDir() throws IOException, InterruptedException {
// When: the fileReaderChannel receives a invalid XML file
fileReaderChannel
.send(MessageBuilder.withPayload(new File(queueDir, INVALID_XML_MOCK_FILE)).build());
// Then: the invalid XML file should be sent to the errorDir
await().until(() -> error.list().length == 1);
}
private void injectProperties() {
ReflectionTestUtils
.setField(directoryManagerService, "errorDir", error.getAbsolutePath().concat("/"));
ReflectionTestUtils
.setField(directoryManagerService, "processedDir", processed.getAbsolutePath().concat("/"));
}
private void moveFilesToQueueDir() throws IOException {
File intFiles = new ClassPathResource(MOCK_FILE_DIR).getFile();
for (String filename : intFiles.list()) {
FileUtils.copyFile(new File(intFiles, filename), new File(queueDir, filename));
}
}
private void createRequiredDirectories() throws IOException {
queueDir = Files.createTempDirectory("queueDir").toFile();
processed = Files.createTempDirectory("processedDir").toFile();
error = Files.createTempDirectory("errorDir").toFile();
}
private void deleteRequiredDirectories() throws IOException {
forceDelete(queueDir);
forceDelete(processed);
forceDelete(error);
}
}
看起來您根本沒有測試或驗證readingInvalidFileAndMoveItToErrorDir()
中的任何內容。 fileReadingEndpoint.start();
是測試方法的最后一行。
請考慮調查什么是 JUnit,我們應該如何編寫測試方法,以及我們應該如何真正驗證異步解決方案,例如與您的類似的集成流程: https : //junit.org/junit5/docs/current/user-guide/
到目前為止我所看到的並不好。 消息源是關於一段時間的輪詢。 您的mockInvalidStudentFile
是關於一直生成相同的文件。 在您的解決方案中真的是預期的嗎? 您可能可以考慮將文件直接發送到為消息源配置的通道。
你不需要fileReadingEndpoint.start();
因為substituteMessageSourceFor()
默認情況下會自動啟動。
將文件作為有效負載發送到頻道后,您的消息將在整個流程中傳播。 您可能應該了解如何在測試中驗證邏輯的正確性。 由於您說您將文件放在某個目錄中的流程末尾,因此您可能應該在發送后檢查該目錄。 甚至可能使用一些循環或 Awaitility: https : //github.com/awaitility/awaitility 。
另一種方法是使用substituteMessageHandlerFor()
這樣您就可以放置一個MockMessageHandler
而不是您的文件MockMessageHandler
來驗證文件。 注意:如果您將原始消息直接發送到通道,而不是作為模擬MessageSource
源,並且您的流程中沒有線程轉移,這將起作用。 因此,所有流程都將在與您的測試方法相同的線程中執行。 如果您在流程中有一些異步切換,您的MockMessageHandler
應該做一些CountDonwLatch
以使測試方法被阻止。
說所有這些,我的意思是在實踐和經驗中測試理解有很多細微差別。 可能無法為您做一些對其他人有用的樣本,因為解決方案可能不同並且需要其他測試方法。
因此,我的建議是:盡你所能,你知道如何測試你自己的解決方案。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.