0
# Assertions and Matchers
1
2
The Elasticsearch test framework provides extensive assertion utilities and custom Hamcrest matchers for validating search results, cluster state, responses, and other Elasticsearch-specific data structures. These utilities enable expressive and maintainable test assertions.
3
4
## ElasticsearchAssertions
5
6
The core assertion utility class providing high-level assertions for Elasticsearch operations and responses.
7
8
```{ .api }
9
package org.elasticsearch.test.hamcrest;
10
11
import org.elasticsearch.action.ActionFuture;
12
import org.elasticsearch.action.ActionResponse;
13
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
14
import org.elasticsearch.action.bulk.BulkResponse;
15
import org.elasticsearch.action.get.GetResponse;
16
import org.elasticsearch.action.search.SearchResponse;
17
import org.elasticsearch.cluster.health.ClusterHealthStatus;
18
import org.elasticsearch.common.bytes.BytesReference;
19
import org.elasticsearch.rest.RestStatus;
20
import org.elasticsearch.search.SearchHit;
21
import org.hamcrest.Matcher;
22
23
/**
24
* Static utility methods for creating Elasticsearch-specific assertions and matchers.
25
* Provides fluent, readable assertions for common Elasticsearch testing scenarios.
26
*/
27
public class ElasticsearchAssertions {
28
29
/**
30
* Asserts that an ActionFuture completes successfully within the default timeout.
31
*
32
* @param future the future to wait for
33
* @param <T> response type
34
* @return the response from the future
35
* @throws AssertionError if future fails or times out
36
*/
37
public static <T extends ActionResponse> T assertAcked(ActionFuture<T> future);
38
39
/**
40
* Asserts that a response indicates an acknowledged operation.
41
*
42
* @param response response to check
43
* @throws AssertionError if operation was not acknowledged
44
*/
45
public static void assertAcked(IsAcknowledgedSupplier response);
46
47
/**
48
* Asserts that a bulk response has no failures.
49
*
50
* @param response bulk response to check
51
* @throws AssertionError if any items in the bulk response failed
52
*/
53
public static void assertNoFailures(BulkResponse response);
54
55
/**
56
* Asserts that a search response completed successfully with no failures.
57
*
58
* @param response search response to check
59
* @throws AssertionError if search had shard failures or timed out
60
*/
61
public static void assertNoFailures(SearchResponse response);
62
63
/**
64
* Asserts that a cluster health response indicates the expected health status.
65
*
66
* @param response cluster health response
67
* @param expectedStatus expected health status
68
* @throws AssertionError if health status doesn't match
69
*/
70
public static void assertHealthStatus(ClusterHealthResponse response,
71
ClusterHealthStatus expectedStatus);
72
73
/**
74
* Asserts that a search response contains the expected number of hits.
75
*
76
* @param response search response to check
77
* @param expectedHits expected total hit count
78
* @throws AssertionError if hit count doesn't match
79
*/
80
public static void assertHitCount(SearchResponse response, long expectedHits);
81
82
/**
83
* Asserts that a search response contains exactly one hit.
84
*
85
* @param response search response to check
86
* @throws AssertionError if hit count is not exactly 1
87
*/
88
public static void assertSingleHit(SearchResponse response);
89
90
/**
91
* Asserts that a search response contains no hits.
92
*
93
* @param response search response to check
94
* @throws AssertionError if any hits are found
95
*/
96
public static void assertNoSearchHits(SearchResponse response);
97
98
/**
99
* Asserts that search hits are ordered according to the specified sort criteria.
100
*
101
* @param response search response to check
102
* @param sortField field used for sorting
103
* @param ascending true if sort should be ascending, false for descending
104
* @throws AssertionError if hits are not properly sorted
105
*/
106
public static void assertOrderedSearchHits(SearchResponse response,
107
String sortField,
108
boolean ascending);
109
110
/**
111
* Asserts that a SearchHit contains a field with the expected value.
112
*
113
* @param hit search hit to check
114
* @param field field name to check
115
* @param expectedValue expected field value
116
* @throws AssertionError if field value doesn't match
117
*/
118
public static void assertSearchHitHasField(SearchHit hit, String field, Object expectedValue);
119
120
/**
121
* Asserts that a GetResponse indicates a document exists.
122
*
123
* @param response get response to check
124
* @throws AssertionError if document doesn't exist
125
*/
126
public static void assertDocumentExists(GetResponse response);
127
128
/**
129
* Asserts that a GetResponse indicates a document does not exist.
130
*
131
* @param response get response to check
132
* @throws AssertionError if document exists
133
*/
134
public static void assertDocumentMissing(GetResponse response);
135
136
/**
137
* Waits for and asserts cluster health reaches the specified status.
138
*
139
* @param client client to use for health checks
140
* @param status expected health status
141
* @param indices indices to check (empty for all indices)
142
*/
143
public static void ensureHealth(Client client,
144
ClusterHealthStatus status,
145
String... indices);
146
147
/**
148
* Waits for cluster to reach green health status.
149
*
150
* @param client client to use for health checks
151
* @param indices indices to check (empty for all indices)
152
*/
153
public static void ensureGreen(Client client, String... indices);
154
155
/**
156
* Waits for cluster to reach yellow or green health status.
157
*
158
* @param client client to use for health checks
159
* @param indices indices to check (empty for all indices)
160
*/
161
public static void ensureYellow(Client client, String... indices);
162
163
/**
164
* Asserts that two BytesReference objects are equal.
165
*
166
* @param expected expected bytes
167
* @param actual actual bytes
168
* @throws AssertionError if bytes don't match
169
*/
170
public static void assertBytesEqual(BytesReference expected, BytesReference actual);
171
172
/**
173
* Asserts that an exception was caused by a specific root cause.
174
*
175
* @param exception exception to examine
176
* @param expectedCause expected root cause class
177
* @throws AssertionError if root cause doesn't match
178
*/
179
public static void assertCausedBy(Throwable exception, Class<? extends Throwable> expectedCause);
180
}
181
```
182
183
## Custom Hamcrest Matchers
184
185
### Search Response Matchers
186
187
```{ .api }
188
package org.elasticsearch.test.hamcrest;
189
190
import org.elasticsearch.action.search.SearchResponse;
191
import org.elasticsearch.search.SearchHit;
192
import org.hamcrest.Matcher;
193
194
/**
195
* Custom Hamcrest matchers for SearchResponse validation.
196
*/
197
public class SearchMatchers {
198
199
/**
200
* Matches SearchResponse with expected hit count.
201
*
202
* @param expectedCount expected number of hits
203
* @return matcher for hit count
204
*/
205
public static Matcher<SearchResponse> hasHitCount(long expectedCount);
206
207
/**
208
* Matches SearchResponse with hit count in specified range.
209
*
210
* @param min minimum hit count (inclusive)
211
* @param max maximum hit count (inclusive)
212
* @return matcher for hit count range
213
*/
214
public static Matcher<SearchResponse> hasHitCount(long min, long max);
215
216
/**
217
* Matches SearchResponse with no search failures.
218
*
219
* @return matcher for successful search
220
*/
221
public static Matcher<SearchResponse> isSuccessfulSearchResponse();
222
223
/**
224
* Matches SearchResponse that completed without timeout.
225
*
226
* @return matcher for non-timed-out search
227
*/
228
public static Matcher<SearchResponse> searchNotTimedOut();
229
230
/**
231
* Matches SearchResponse containing a hit with specified ID.
232
*
233
* @param id document ID to match
234
* @return matcher for document ID
235
*/
236
public static Matcher<SearchResponse> hasHitWithId(String id);
237
238
/**
239
* Matches SearchResponse where hits are ordered by specified field.
240
*
241
* @param field field name used for ordering
242
* @param ascending true for ascending order, false for descending
243
* @return matcher for search hit ordering
244
*/
245
public static Matcher<SearchResponse> orderedBy(String field, boolean ascending);
246
247
/**
248
* Matches SearchHit containing a field with the expected value.
249
*
250
* @param fieldName field name to check
251
* @param expectedValue expected field value
252
* @return matcher for search hit field
253
*/
254
public static Matcher<SearchHit> hasField(String fieldName, Object expectedValue);
255
256
/**
257
* Matches SearchHit with expected document ID.
258
*
259
* @param expectedId expected document ID
260
* @return matcher for document ID
261
*/
262
public static Matcher<SearchHit> hasId(String expectedId);
263
264
/**
265
* Matches SearchHit with expected document type.
266
*
267
* @param expectedType expected document type
268
* @return matcher for document type
269
*/
270
public static Matcher<SearchHit> hasType(String expectedType);
271
272
/**
273
* Matches SearchHit with expected index name.
274
*
275
* @param expectedIndex expected index name
276
* @return matcher for index name
277
*/
278
public static Matcher<SearchHit> hasIndex(String expectedIndex);
279
280
/**
281
* Matches SearchHit with score greater than or equal to minimum.
282
*
283
* @param minScore minimum score threshold
284
* @return matcher for minimum score
285
*/
286
public static Matcher<SearchHit> hasScore(float minScore);
287
288
/**
289
* Matches SearchHit containing highlighted fields.
290
*
291
* @param fieldName field that should be highlighted
292
* @return matcher for highlighted field
293
*/
294
public static Matcher<SearchHit> hasHighlighting(String fieldName);
295
}
296
```
297
298
### Cluster Health Matchers
299
300
```{ .api }
301
package org.elasticsearch.test.hamcrest;
302
303
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
304
import org.elasticsearch.cluster.health.ClusterHealthStatus;
305
import org.hamcrest.Matcher;
306
307
/**
308
* Hamcrest matchers for cluster health assertions.
309
*/
310
public class ClusterHealthMatchers {
311
312
/**
313
* Matches ClusterHealthResponse with expected status.
314
*
315
* @param expectedStatus expected cluster health status
316
* @return matcher for health status
317
*/
318
public static Matcher<ClusterHealthResponse> hasStatus(ClusterHealthStatus expectedStatus);
319
320
/**
321
* Matches ClusterHealthResponse indicating cluster is not timed out.
322
*
323
* @return matcher for non-timed-out cluster
324
*/
325
public static Matcher<ClusterHealthResponse> isNotTimedOut();
326
327
/**
328
* Matches ClusterHealthResponse with expected number of nodes.
329
*
330
* @param expectedNodes expected node count
331
* @return matcher for node count
332
*/
333
public static Matcher<ClusterHealthResponse> hasNodes(int expectedNodes);
334
335
/**
336
* Matches ClusterHealthResponse with expected number of data nodes.
337
*
338
* @param expectedDataNodes expected data node count
339
* @return matcher for data node count
340
*/
341
public static Matcher<ClusterHealthResponse> hasDataNodes(int expectedDataNodes);
342
343
/**
344
* Matches ClusterHealthResponse with no relocating shards.
345
*
346
* @return matcher for no relocating shards
347
*/
348
public static Matcher<ClusterHealthResponse> hasNoRelocatingShards();
349
350
/**
351
* Matches ClusterHealthResponse with no initializing shards.
352
*
353
* @return matcher for no initializing shards
354
*/
355
public static Matcher<ClusterHealthResponse> hasNoInitializingShards();
356
357
/**
358
* Matches ClusterHealthResponse with no unassigned shards.
359
*
360
* @return matcher for no unassigned shards
361
*/
362
public static Matcher<ClusterHealthResponse> hasNoUnassignedShards();
363
}
364
```
365
366
### Optional and Collection Matchers
367
368
```{ .api }
369
package org.elasticsearch.test.hamcrest;
370
371
import java.util.Collection;
372
import java.util.Optional;
373
import org.hamcrest.Matcher;
374
375
/**
376
* Hamcrest matchers for Optional and Collection types.
377
*/
378
public class OptionalMatchers {
379
380
/**
381
* Matches Optional that is present.
382
*
383
* @param <T> type of Optional content
384
* @return matcher for present Optional
385
*/
386
public static <T> Matcher<Optional<T>> isPresent();
387
388
/**
389
* Matches Optional that is present and contains the expected value.
390
*
391
* @param expectedValue expected Optional content
392
* @param <T> type of Optional content
393
* @return matcher for Optional with specific value
394
*/
395
public static <T> Matcher<Optional<T>> isPresentWith(T expectedValue);
396
397
/**
398
* Matches Optional that is empty.
399
*
400
* @param <T> type of Optional content
401
* @return matcher for empty Optional
402
*/
403
public static <T> Matcher<Optional<T>> isEmpty();
404
405
/**
406
* Matches Collection containing all specified items.
407
*
408
* @param items items that should be present
409
* @param <T> type of Collection elements
410
* @return matcher for Collection containing items
411
*/
412
public static <T> Matcher<Collection<T>> containsAll(T... items);
413
414
/**
415
* Matches Collection with expected size.
416
*
417
* @param expectedSize expected collection size
418
* @param <T> type of Collection elements
419
* @return matcher for Collection size
420
*/
421
public static <T> Matcher<Collection<T>> hasSize(int expectedSize);
422
423
/**
424
* Matches empty Collection.
425
*
426
* @param <T> type of Collection elements
427
* @return matcher for empty Collection
428
*/
429
public static <T> Matcher<Collection<T>> emptyCollection();
430
}
431
```
432
433
### Tuple and Rectangle Matchers
434
435
```{ .api }
436
package org.elasticsearch.test.hamcrest;
437
438
import org.elasticsearch.common.geo.GeoPoint;
439
import org.elasticsearch.geometry.Rectangle;
440
import org.elasticsearch.common.collect.Tuple;
441
import org.hamcrest.Matcher;
442
443
/**
444
* Specialized matchers for geometric and tuple data structures.
445
*/
446
public class TupleMatchers {
447
448
/**
449
* Matches Tuple with expected first element.
450
*
451
* @param expectedFirst expected first tuple element
452
* @param <T1> type of first element
453
* @param <T2> type of second element
454
* @return matcher for first tuple element
455
*/
456
public static <T1, T2> Matcher<Tuple<T1, T2>> hasFirst(T1 expectedFirst);
457
458
/**
459
* Matches Tuple with expected second element.
460
*
461
* @param expectedSecond expected second tuple element
462
* @param <T1> type of first element
463
* @param <T2> type of second element
464
* @return matcher for second tuple element
465
*/
466
public static <T1, T2> Matcher<Tuple<T1, T2>> hasSecond(T2 expectedSecond);
467
468
/**
469
* Matches Tuple with both expected elements.
470
*
471
* @param expectedFirst expected first element
472
* @param expectedSecond expected second element
473
* @param <T1> type of first element
474
* @param <T2> type of second element
475
* @return matcher for complete tuple
476
*/
477
public static <T1, T2> Matcher<Tuple<T1, T2>> isTuple(T1 expectedFirst, T2 expectedSecond);
478
}
479
480
/**
481
* Matchers for geometric Rectangle objects.
482
*/
483
public class RectangleMatchers {
484
485
/**
486
* Matches Rectangle containing the specified point.
487
*
488
* @param point point that should be contained
489
* @return matcher for point containment
490
*/
491
public static Matcher<Rectangle> containsPoint(GeoPoint point);
492
493
/**
494
* Matches Rectangle with expected bounds.
495
*
496
* @param minLon minimum longitude
497
* @param maxLon maximum longitude
498
* @param minLat minimum latitude
499
* @param maxLat maximum latitude
500
* @return matcher for rectangle bounds
501
*/
502
public static Matcher<Rectangle> hasBounds(double minLon, double maxLon,
503
double minLat, double maxLat);
504
505
/**
506
* Matches Rectangle that intersects with another rectangle.
507
*
508
* @param other rectangle to check intersection with
509
* @return matcher for rectangle intersection
510
*/
511
public static Matcher<Rectangle> intersects(Rectangle other);
512
}
513
```
514
515
## Usage Examples
516
517
### Search Assertion Examples
518
519
```java
520
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.*;
521
import static org.elasticsearch.test.hamcrest.SearchMatchers.*;
522
523
public class SearchAssertionTest extends ESIntegTestCase {
524
525
public void testSearchAssertions() {
526
createIndex("test-index");
527
528
// Index test documents
529
client().prepareIndex("test-index")
530
.setId("1")
531
.setSource("title", "First Document", "score", 85)
532
.get();
533
client().prepareIndex("test-index")
534
.setId("2")
535
.setSource("title", "Second Document", "score", 92)
536
.get();
537
538
refresh("test-index");
539
540
// Search and use assertions
541
SearchResponse response = client().prepareSearch("test-index")
542
.setQuery(QueryBuilders.matchAllQuery())
543
.addSort("score", SortOrder.DESC)
544
.get();
545
546
// Basic assertions
547
assertNoFailures(response);
548
assertHitCount(response, 2);
549
550
// Hamcrest matchers
551
assertThat(response, hasHitCount(2));
552
assertThat(response, isSuccessfulSearchResponse());
553
assertThat(response, hasHitWithId("1"));
554
assertThat(response, orderedBy("score", false)); // descending
555
556
// Individual hit assertions
557
SearchHit firstHit = response.getHits().getHits()[0];
558
assertThat(firstHit, hasId("2")); // Higher score document first
559
assertThat(firstHit, hasField("title", "Second Document"));
560
assertThat(firstHit, hasScore(0.0f)); // Match all query has score
561
562
// Combined assertions
563
assertSingleHit(client().prepareSearch("test-index")
564
.setQuery(QueryBuilders.termQuery("title", "first"))
565
.get());
566
}
567
568
public void testClusterHealthAssertions() {
569
createIndex("health-test");
570
571
// Wait for and assert cluster health
572
ensureGreen("health-test");
573
574
// Manual health check with assertions
575
ClusterHealthResponse health = client().admin().cluster()
576
.prepareHealth("health-test")
577
.get();
578
579
assertHealthStatus(health, ClusterHealthStatus.GREEN);
580
assertThat(health, hasStatus(ClusterHealthStatus.GREEN));
581
assertThat(health, isNotTimedOut());
582
assertThat(health, hasNoUnassignedShards());
583
assertThat(health, hasDataNodes(numDataNodes()));
584
}
585
586
public void testBulkOperationAssertions() {
587
createIndex("bulk-test");
588
589
BulkRequestBuilder bulk = client().prepareBulk();
590
for (int i = 0; i < 10; i++) {
591
bulk.add(client().prepareIndex("bulk-test")
592
.setId(String.valueOf(i))
593
.setSource("field", "value" + i));
594
}
595
596
BulkResponse bulkResponse = bulk.get();
597
598
// Assert no bulk failures
599
assertNoFailures(bulkResponse);
600
assertThat("All items should succeed",
601
bulkResponse.hasFailures(), equalTo(false));
602
assertThat("Should have 10 items",
603
bulkResponse.getItems().length, equalTo(10));
604
}
605
}
606
```
607
608
### Custom Matcher Usage
609
610
```java
611
import static org.elasticsearch.test.hamcrest.OptionalMatchers.*;
612
import static org.elasticsearch.test.hamcrest.TupleMatchers.*;
613
614
public class CustomMatcherTest extends ESTestCase {
615
616
public void testOptionalMatchers() {
617
Optional<String> presentValue = Optional.of("test");
618
Optional<String> emptyValue = Optional.empty();
619
620
assertThat(presentValue, isPresent());
621
assertThat(presentValue, isPresentWith("test"));
622
assertThat(emptyValue, isEmpty());
623
}
624
625
public void testTupleMatchers() {
626
Tuple<String, Integer> tuple = new Tuple<>("hello", 42);
627
628
assertThat(tuple, hasFirst("hello"));
629
assertThat(tuple, hasSecond(42));
630
assertThat(tuple, isTuple("hello", 42));
631
}
632
633
public void testCollectionMatchers() {
634
List<String> items = Arrays.asList("a", "b", "c");
635
636
assertThat(items, hasSize(3));
637
assertThat(items, containsAll("a", "c"));
638
assertThat(Collections.emptyList(), emptyCollection());
639
}
640
}
641
```
642
643
### Exception and Error Assertion Examples
644
645
```java
646
public class ExceptionAssertionTest extends ESTestCase {
647
648
public void testExceptionAssertions() {
649
// Test expected exceptions with root cause checking
650
ElasticsearchException ex = expectThrows(ElasticsearchException.class, () -> {
651
throw new ElasticsearchException("Test error",
652
new IllegalArgumentException("Root cause"));
653
});
654
655
assertCausedBy(ex, IllegalArgumentException.class);
656
assertThat(ex.getMessage(), containsString("Test error"));
657
}
658
659
public void testSearchFailureAssertions() {
660
createIndex("test-index");
661
662
// Test search with invalid query
663
SearchPhaseExecutionException ex = expectThrows(
664
SearchPhaseExecutionException.class,
665
() -> client().prepareSearch("test-index")
666
.setQuery(QueryBuilders.scriptQuery(
667
new Script("invalid script that will fail")))
668
.get()
669
);
670
671
assertThat("Should have shard failures",
672
ex.shardFailures().length, greaterThan(0));
673
}
674
}
675
```
676
677
## Best Practices
678
679
### Assertion Selection
680
- Use `ElasticsearchAssertions` static methods for common scenarios
681
- Prefer Hamcrest matchers for complex or reusable assertions
682
- Combine basic assertions with custom matchers for comprehensive validation
683
684
### Readability
685
- Use descriptive matcher names that clearly express intent
686
- Chain matchers with `allOf()` and `anyOf()` for complex conditions
687
- Include meaningful assertion messages for better test failure diagnostics
688
689
### Performance
690
- Avoid overly complex matcher chains that impact test performance
691
- Use targeted assertions rather than comprehensive validation when appropriate
692
- Cache frequently used matchers in test utilities
693
694
### Maintainability
695
- Create custom matchers for domain-specific assertion patterns
696
- Extract common assertion patterns into utility methods
697
- Keep assertion logic close to the test scenarios that use them
698
699
The assertion and matcher utilities provide a rich vocabulary for expressing test expectations clearly and maintaining readable, reliable test code.