वहाँ से बाहर आने वाले सभी स्प्रिंग उपयोगकर्ताओं के लिए, यह आमतौर पर आजकल मैं अपना एकीकरण परीक्षण करता हूं, जहां async व्यवहार शामिल है:
उत्पादन कोड में एक एप्लिकेशन ईवेंट को फायर करें, जब एक एसिंक्स कार्य (जैसे कि I / O कॉल) समाप्त हो गया है। अधिकांश समय यह घटना वैसे भी आवश्यक होती है जब उत्पादन में एसिंक्श ऑपरेशन की प्रतिक्रिया को संभालने के लिए।
इस घटना के बाद, आप अपने परीक्षण के मामले में निम्नलिखित रणनीति का उपयोग कर सकते हैं:
- परीक्षण के तहत प्रणाली निष्पादित करें
- घटना के लिए सुनो और सुनिश्चित करें कि घटना को निकाल दिया गया है
- अपनी मुखरता करो
इसे नीचे तोड़ने के लिए, आपको सबसे पहले आग लगने के लिए किसी प्रकार के डोमेन इवेंट की आवश्यकता होगी। मैं उस UUID का उपयोग कर रहा हूं जो उस कार्य को पहचानने के लिए है जो पूरा हो गया है, लेकिन आप निश्चित रूप से कुछ और का उपयोग करने के लिए स्वतंत्र हैं जब तक यह अद्वितीय है।
(ध्यान दें, कि निम्न कोड स्निपेट बॉयलर प्लेट कोड से छुटकारा पाने के लिए लोम्बोक एनोटेशन का भी उपयोग करते हैं )
@RequiredArgsConstructor
class TaskCompletedEvent() {
private final UUID taskId;
// add more fields containing the result of the task if required
}
उत्पादन कोड स्वयं तो आमतौर पर इस तरह दिखता है:
@Component
@RequiredArgsConstructor
class Production {
private final ApplicationEventPublisher eventPublisher;
void doSomeTask(UUID taskId) {
// do something like calling a REST endpoint asynchronously
eventPublisher.publishEvent(new TaskCompletedEvent(taskId));
}
}
मैं @EventListener
परीक्षण कोड में प्रकाशित घटना को पकड़ने के लिए स्प्रिंग का उपयोग कर सकता हूं । ईवेंट श्रोता थोड़ा अधिक शामिल है, क्योंकि इसे दो मामलों को एक थ्रेड से सुरक्षित तरीके से संभालना है:
- उत्पादन कोड परीक्षण मामले की तुलना में तेज है और घटना के लिए परीक्षण मामले की जांच से पहले ही घटना को निकाल दिया गया है, या
- टेस्ट केस प्रोडक्शन कोड से तेज है और टेस्ट केस को इवेंट का इंतजार करना पड़ता है।
ए CountDownLatch
का उपयोग दूसरे मामले के लिए किया जाता है जैसा कि यहां अन्य उत्तरों में वर्णित है। यह भी ध्यान दें, कि @Order
ईवेंट हैंडलर विधि पर एनोटेशन यह सुनिश्चित करता है, कि इस ईवेंट हैंडलर विधि को किसी अन्य ईवेंट श्रोता के उत्पादन में उपयोग करने के बाद कहा जाता है।
@Component
class TaskCompletionEventListener {
private Map<UUID, CountDownLatch> waitLatches = new ConcurrentHashMap<>();
private List<UUID> eventsReceived = new ArrayList<>();
void waitForCompletion(UUID taskId) {
synchronized (this) {
if (eventAlreadyReceived(taskId)) {
return;
}
checkNobodyIsWaiting(taskId);
createLatch(taskId);
}
waitForEvent(taskId);
}
private void checkNobodyIsWaiting(UUID taskId) {
if (waitLatches.containsKey(taskId)) {
throw new IllegalArgumentException("Only one waiting test per task ID supported, but another test is already waiting for " + taskId + " to complete.");
}
}
private boolean eventAlreadyReceived(UUID taskId) {
return eventsReceived.remove(taskId);
}
private void createLatch(UUID taskId) {
waitLatches.put(taskId, new CountDownLatch(1));
}
@SneakyThrows
private void waitForEvent(UUID taskId) {
var latch = waitLatches.get(taskId);
latch.await();
}
@EventListener
@Order
void eventReceived(TaskCompletedEvent event) {
var taskId = event.getTaskId();
synchronized (this) {
if (isSomebodyWaiting(taskId)) {
notifyWaitingTest(taskId);
} else {
eventsReceived.add(taskId);
}
}
}
private boolean isSomebodyWaiting(UUID taskId) {
return waitLatches.containsKey(taskId);
}
private void notifyWaitingTest(UUID taskId) {
var latch = waitLatches.remove(taskId);
latch.countDown();
}
}
अंतिम चरण एक परीक्षण मामले में परीक्षण के तहत प्रणाली को निष्पादित करना है। मैं यहां JUnit 5 के साथ स्प्रिंगबूट परीक्षण का उपयोग कर रहा हूं, लेकिन स्प्रिंग संदर्भ का उपयोग करके सभी परीक्षणों के लिए यह एक ही काम करना चाहिए।
@SpringBootTest
class ProductionIntegrationTest {
@Autowired
private Production sut;
@Autowired
private TaskCompletionEventListener listener;
@Test
void thatTaskCompletesSuccessfully() {
var taskId = UUID.randomUUID();
sut.doSomeTask(taskId);
listener.waitForCompletion(taskId);
// do some assertions like looking into the DB if value was stored successfully
}
}
ध्यान दें, कि यहां अन्य उत्तरों के विपरीत, यह समाधान भी काम करेगा यदि आप समानांतर और कई थ्रेड्स में अपने परीक्षणों को निष्पादित करते हैं और एक ही समय में async कोड का उपयोग करते हैं।