Test framework library for Elasticsearch providing comprehensive testing utilities, base test classes, cluster management, and assertion helpers for unit and integration testing of Elasticsearch plugins and applications
—
The core testing classes in org.elasticsearch.test provide the foundation for all Elasticsearch testing. These base classes extend Apache Lucene's testing infrastructure with Elasticsearch-specific functionality, random data generation, and cluster management capabilities.
The primary base class for all Elasticsearch unit tests, extending Lucene's LuceneTestCase with Elasticsearch-specific utilities.
package org.elasticsearch.test;
import com.carrotsearch.randomizedtesting.RandomizedTest;
import org.apache.lucene.tests.util.LuceneTestCase;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.core.TimeValue;
/**
* Base class for all Elasticsearch unit tests providing randomized testing utilities,
* proper setup/teardown lifecycle, and Elasticsearch-specific test infrastructure.
*/
@ThreadLeakScope(Scope.NONE)
@ThreadLeakFilters(filters = {ESTestCase.ElasticsearchThreadFilter.class})
@TimeoutSuite(millis = TimeUnits.HOUR)
@LuceneTestCase.SuppressReproduceLine
public abstract class ESTestCase extends LuceneTestCase {
/**
* Sets up test environment before each test method.
* Initializes logging, thread context, and randomization seeds.
*/
@Before
public void setUp() throws Exception;
/**
* Cleans up test environment after each test method.
* Ensures proper resource cleanup and thread termination.
*/
@After
public void tearDown() throws Exception;
/**
* Generates a random string of the specified length using alphanumeric characters.
*
* @param length the length of the string to generate
* @return a random alphanumeric string
*/
public static String randomAlphaOfLength(int length);
/**
* Generates a random string between the specified minimum and maximum length.
*
* @param minLength minimum length (inclusive)
* @param maxLength maximum length (inclusive)
* @return a random alphanumeric string
*/
public static String randomAlphaOfLengthBetween(int minLength, int maxLength);
/**
* Returns a random integer between min and max (both inclusive).
*
* @param min minimum value (inclusive)
* @param max maximum value (inclusive)
* @return random integer in the specified range
*/
public static int randomIntBetween(int min, int max);
/**
* Returns a random integer scaled by the test multiplier for performance testing.
*
* @param min minimum value (inclusive)
* @param max maximum value (inclusive)
* @return scaled random integer
*/
public static int scaledRandomIntBetween(int min, int max);
/**
* Picks a random element from the provided array.
*
* @param array array to pick from
* @param <T> type of array elements
* @return randomly selected element
*/
public static <T> T randomFrom(T... array);
/**
* Picks a random element from the provided list.
*
* @param list list to pick from
* @param <T> type of list elements
* @return randomly selected element
*/
public static <T> T randomFrom(List<T> list);
/**
* Returns a random boolean value.
*
* @return true or false with equal probability
*/
public static boolean randomBoolean();
/**
* Returns a random TimeValue for duration testing.
*
* @return random TimeValue between 0 and 1 hour
*/
public static TimeValue randomTimeValue();
/**
* Returns a random long value.
*
* @return random long value
*/
public static long randomLong();
/**
* Returns a random non-negative long value.
*
* @return random non-negative long value
*/
public static long randomNonNegativeLong();
/**
* Returns a random double value.
*
* @return random double value
*/
public static double randomDouble();
/**
* Returns a random float value.
*
* @return random float value
*/
public static float randomFloat();
/**
* Returns a random byte value.
*
* @return random byte value
*/
public static byte randomByte();
/**
* Returns a random UUID string.
*
* @return random UUID string
*/
public static String randomUUID();
/**
* Returns a random identifier string suitable for names/IDs.
*
* @return random identifier string (8-12 lowercase chars)
*/
public static String randomIdentifier();
/**
* Returns a random alphanumeric string of the specified length.
*
* @param length the length of the string to generate
* @return random alphanumeric string
*/
public static String randomAlphanumericOfLength(int length);
/**
* Returns a random unicode string of the specified length.
*
* @param codeUnits the length in code units
* @return random unicode string
*/
public static String randomUnicodeOfLength(int codeUnits);
/**
* Returns a random value other than the specified input.
*
* @param input value to exclude
* @param randomSupplier supplier for generating random values
* @param <T> type of the value
* @return random value different from input
*/
public static <T> T randomValueOtherThan(T input, Supplier<T> randomSupplier);
/**
* Returns a random subset of the provided collection.
*
* @param collection collection to select from
* @param <T> type of collection elements
* @return random subset as a list
*/
public static <T> List<T> randomSubsetOf(Collection<T> collection);
/**
* Waits until the specified condition becomes true or timeout is reached.
*
* @param breakSupplier condition to wait for
* @param maxWaitTime maximum time to wait
* @param unit time unit for maxWaitTime
* @return true if condition was met, false if timeout
*/
public static boolean waitUntil(BooleanSupplier breakSupplier, long maxWaitTime, TimeUnit unit);
/**
* Waits until the specified condition becomes true (10 second default timeout).
*
* @param breakSupplier condition to wait for
* @return true if condition was met, false if timeout
*/
public static boolean waitUntil(BooleanSupplier breakSupplier);
}import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.common.settings.Settings;
public class MyUnitTest extends ESTestCase {
public void testRandomDataGeneration() {
// Generate random test data
String randomName = randomAlphaOfLength(10);
int randomPort = randomIntBetween(9200, 9300);
boolean randomFlag = randomBoolean();
// Test with random values
MyService service = new MyService(randomName, randomPort, randomFlag);
assertThat(service.getName(), equalTo(randomName));
assertThat(service.getPort(), allOf(greaterThanOrEqualTo(9200), lessThanOrEqualTo(9300)));
}
public void testSettingsGeneration() {
Settings settings = Settings.builder()
.put("node.name", randomAlphaOfLength(8))
.put("http.port", randomIntBetween(9200, 9299))
.put("path.home", createTempDir())
.build();
assertThat(settings.get("node.name"), notNullValue());
assertThat(settings.getAsInt("http.port", 0), greaterThan(9199));
}
public void testRandomValueGeneration() {
// Generate various random values
String uuid = randomUUID();
String identifier = randomIdentifier();
long randomLong = randomLong();
double randomDouble = randomDouble();
byte randomByte = randomByte();
assertThat(uuid, notNullValue());
assertThat(identifier.length(), allOf(greaterThanOrEqualTo(8), lessThanOrEqualTo(12)));
}
public void testAsyncOperation() {
AtomicBoolean completed = new AtomicBoolean(false);
// Start async operation
myAsyncMethod(() -> completed.set(true));
// Wait for completion (30 second timeout)
boolean success = waitUntil(() -> completed.get(), 30, TimeUnit.SECONDS);
assertTrue("Operation should complete", success && completed.get());
}
}Base class for integration tests that require a full Elasticsearch cluster. Manages cluster lifecycle and provides integration testing utilities.
package org.elasticsearch.test;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
import org.elasticsearch.client.internal.Client;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.elasticsearch.common.settings.Settings;
/**
* Base class for integration tests providing full Elasticsearch cluster management.
* Supports single-node and multi-node cluster configurations with automatic lifecycle management.
*/
@ClusterScope(scope = Scope.SUITE, numDataNodes = 1)
public abstract class ESIntegTestCase extends ESTestCase {
/**
* Defines the scope and configuration for test clusters.
*/
public static @interface ClusterScope {
Scope scope() default Scope.SUITE;
int numDataNodes() default -1;
int numClientNodes() default 0;
boolean autoManageMasterNodes() default true;
String[] supportedFeatures() default {};
}
/**
* Enumeration of cluster scope options.
*/
public enum Scope {
/** Cluster shared across entire test suite */
SUITE,
/** New cluster for each test method */
TEST
}
/**
* Returns the client for interacting with the test cluster.
*
* @return client instance connected to the test cluster
*/
public static Client client();
/**
* Returns a client connected to a specific node.
*
* @param node node name or pattern
* @return client for the specified node
*/
public static Client client(String node);
/**
* Returns the internal test cluster for direct cluster management.
*
* @return InternalTestCluster instance
*/
public static InternalTestCluster internalCluster();
/**
* Creates an index with the specified name and default settings.
*
* @param indices index names to create
*/
public static void createIndex(String... indices);
/**
* Creates an index with custom settings.
*
* @param index index name
* @param settings index settings
*/
public static void createIndex(String index, Settings settings);
/**
* Creates an index using a builder for complex configuration.
*
* @param name index name
* @return CreateIndexRequestBuilder for fluent configuration
*/
public static CreateIndexRequestBuilder prepareCreate(String name);
/**
* Waits for the cluster to reach green health status for the specified indices.
*
* @param indices indices to wait for (empty means all indices)
*/
public static void ensureGreen(String... indices);
/**
* Waits for the cluster to reach yellow or green health status.
*
* @param indices indices to wait for (empty means all indices)
*/
public static void ensureYellow(String... indices);
/**
* Waits for the cluster to have the specified health status.
*
* @param status required health status
* @param indices indices to check
*/
public static void ensureHealth(ClusterHealthStatus status, String... indices);
/**
* Refreshes the specified indices to make documents searchable.
*
* @param indices indices to refresh (empty means all indices)
*/
public static void refresh(String... indices);
/**
* Flushes the specified indices to persist changes to disk.
*
* @param indices indices to flush (empty means all indices)
*/
public static void flush(String... indices);
/**
* Waits for all pending cluster tasks to complete.
*
* @param timeout maximum time to wait
*/
public static void waitForPendingTasks(TimeValue timeout);
/**
* Returns the number of nodes in the cluster.
*
* @return node count
*/
public static int numNodes();
/**
* Returns settings that should be applied to all nodes in the cluster.
* Override this method to customize node configuration.
*
* @return settings for cluster nodes
*/
protected Settings nodeSettings(int nodeOrdinal);
/**
* Returns additional plugins that should be installed on cluster nodes.
*
* @return collection of plugin classes
*/
protected Collection<Class<? extends Plugin>> nodePlugins();
}import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.QueryBuilders;
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 3)
public class MyIntegrationTest extends ESIntegTestCase {
public void testIndexAndSearch() {
// Create index with custom settings
createIndex("test-index", Settings.builder()
.put("number_of_shards", 2)
.put("number_of_replicas", 1)
.build());
ensureGreen("test-index");
// Index some documents
for (int i = 0; i < 10; i++) {
client().prepareIndex("test-index")
.setId(String.valueOf(i))
.setSource("field1", "value" + i, "field2", i)
.get();
}
refresh("test-index");
// Search and verify results
SearchResponse response = client().prepareSearch("test-index")
.setQuery(QueryBuilders.rangeQuery("field2").gte(5))
.get();
assertThat(response.getHits().getTotalHits().value, equalTo(5L));
}
public void testClusterHealth() {
createIndex("health-test");
// Wait for green status
ensureGreen("health-test");
// Verify cluster is healthy
ClusterHealthResponse health = client().admin().cluster()
.prepareHealth("health-test")
.get();
assertThat(health.getStatus(), equalTo(ClusterHealthStatus.GREEN));
assertThat(health.getNumberOfDataNodes(), greaterThanOrEqualTo(1));
}
@Override
protected Settings nodeSettings(int nodeOrdinal) {
return Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put("node.attr.custom", "value-" + nodeOrdinal)
.build();
}
@Override
protected Collection<Class<? extends Plugin>> nodePlugins() {
return Arrays.asList(MyTestPlugin.class);
}
}Lightweight integration testing base class that runs tests against a single Elasticsearch node. More efficient than full cluster tests when cluster features are not needed.
package org.elasticsearch.test;
import org.elasticsearch.client.internal.Client;
import org.elasticsearch.node.Node;
/**
* Base class for integration tests that require only a single Elasticsearch node.
* Provides faster test execution compared to full cluster tests while still offering
* integration testing capabilities.
*/
public abstract class SingleNodeTestCase extends ESTestCase {
/**
* Returns the client connected to the single test node.
*
* @return client for the test node
*/
protected static Client client();
/**
* Returns the test node instance.
*
* @return Node instance for direct node access
*/
protected static Node node();
/**
* Creates an index with the specified name.
*
* @param index index name
*/
protected static void createIndex(String index);
/**
* Creates an index with custom settings and mappings.
*
* @param index index name
* @param settings index settings
* @param mappings index mappings
*/
protected static void createIndex(String index, Settings settings, String mappings);
/**
* Returns settings for the test node. Override to customize node configuration.
*
* @return settings for the single test node
*/
protected Settings nodeSettings();
/**
* Returns plugins to install on the test node.
*
* @return collection of plugin classes
*/
protected Collection<Class<? extends Plugin>> getPlugins();
}import org.elasticsearch.test.SingleNodeTestCase;
import org.elasticsearch.action.get.GetResponse;
public class MySingleNodeTest extends SingleNodeTestCase {
public void testBasicIndexing() {
createIndex("single-node-test");
// Index a document
client().prepareIndex("single-node-test")
.setId("1")
.setSource("title", "Test Document", "content", "This is a test")
.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
.get();
// Retrieve and verify
GetResponse response = client().prepareGet("single-node-test", "1").get();
assertTrue("Document should exist", response.isExists());
assertThat(response.getSource().get("title"), equalTo("Test Document"));
}
@Override
protected Settings nodeSettings() {
return Settings.builder()
.put(super.nodeSettings())
.put("xpack.security.enabled", false)
.put("xpack.monitoring.enabled", false)
.build();
}
}Abstract interface for cluster management used by integration test base classes.
package org.elasticsearch.test;
import org.elasticsearch.client.internal.Client;
import org.elasticsearch.cluster.node.DiscoveryNode;
/**
* Interface for managing test clusters. Provides abstraction over cluster lifecycle
* and client management for different cluster implementations.
*/
public interface TestCluster extends Closeable {
/**
* Returns a client connected to the cluster.
*
* @return cluster client
*/
Client client();
/**
* Returns the number of nodes in the cluster.
*
* @return node count
*/
int size();
/**
* Returns the number of data nodes in the cluster.
*
* @return data node count
*/
int numDataNodes();
/**
* Returns the cluster name.
*
* @return cluster name
*/
String getClusterName();
/**
* Returns information about all nodes in the cluster.
*
* @return iterable of discovery nodes
*/
Iterable<DiscoveryNode> getNodes();
/**
* Starts the cluster if not already running.
*/
void beforeTest(Random random, double transportClientRatio);
/**
* Performs cleanup after test execution.
*/
void afterTest();
/**
* Wipes the cluster state clean between tests.
*/
void wipe(Set<String> excludeTemplates);
}super.setUp() and super.tearDown() in custom setup/teardown methodsSingleNodeTestCase when cluster features are not neededScope.TEST over Scope.SUITE when appropriate@ClusterScope annotations to configure optimal cluster sizeThe core testing classes provide a solid foundation for all Elasticsearch testing scenarios, from simple unit tests to complex integration scenarios involving multiple nodes and cluster management.
Install with Tessl CLI
npx tessl i tessl/maven-org-elasticsearch--elasticsearch-test-framework