Testing Spring Data Couchbase Apps With TestContainers
Docker doesn't always play nicely with your apps, but learning how to configure your work for testing can help ease the pain.
Join the DZone community and get the full member experience.
Join For FreeIn a previous series of blog posts, I explained how to use TestContainers for your Java JUnit tests. Some of the issues we did not address were about how to test N1QL, create your own buckets, index, etc. This post will be about building Spring Data Couchbase test cases and cover theses questions we left out.
Hardwire Unconfigurable Ports
One of the limitations we currently have on Couchbase Server is that you cannot change some of the default port. This is a problem with Docker, as it’s changing ports only notified otherwise. This can be great because it means you can have several Couchbase instances running on the same machine. But unfortunately, that won’t work, so some ports will have to be fixed. This can be declared fairly easily with TestContainers using the addFixedExposedPort method.
@Override
protected void configure() {
addExposedPorts(8091, 11207, 11210, 11211, 18091, 18092, 18093);
addFixedExposedPort(8092, 8092);
addFixedExposedPort(8093, 8093);
addFixedExposedPort(8094, 8094);
addFixedExposedPort(8095, 8095);
setWaitStrategy(new HttpWaitStrategy().forPath("/ui/index.html#/"));
}
With that out of the way, our Java SDK will be able to connect to N1QL.
Abstract Spring Data Couchbase Docker Test Case
The goal here is to create an abstract test case that will be used by any class that needs a Couchbase instance and Spring Data Couchbase configured. It starts as in the previous posts by instantiating a CouchbaseContainer field. Since we are testing Spring Data we configure support for Index, Query and let’s throw in FTS for later.
To make sure this class will run tests for your application, add the @RunWith(SpringRunner.class)
annotation. And to make sure your application configuration is tested as well as our custom configuration, add @SpringBootTest(classes = {GittalentBackendApplication.class,
AbstractSPDataTestConfig.CouchbaseTestConfig.class})
.
Now talking about custom configuration, what do we need? We want to override the default Couchbase configuration of the app. To do so we need to implement a CouchbaseConfigurer. This interface defines all the bean needed for Spring Data Couchbase to work properly. It provides instances for CouchbaseEnvironment, ClusterInfo, Cluster, and Bucket.
They will all come from our CouchbaseContainer setup before running the tests. So we need to make sure that the Container is running and ready before initializing all the beans. This can be achieved by adding an init() method annotated with @PostConstruct. This will allow us to first make sure the container is running, then set up additional stuff. In the following example we setup a bucket called default and setup the Index type to be MOI.
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {GittalentBackendApplication.class, AbstractSPDataTestConfig.CouchbaseTestConfig.class})
public abstract class AbstractSPDataTestConfig {
public static final String clusterUser = "Administrator";
public static final String clusterPassword = "password";
@ClassRule
public static CouchbaseContainer couchbaseContainer = new CouchbaseContainer()
.withFTS(true)
.withIndex(true)
.withQuery(true)
.withClusterUsername(clusterUser)
.withClusterPassword(clusterPassword);
@Configuration
static class CouchbaseTestConfig implements CouchbaseConfigurer {
private CouchbaseContainer couchbaseContainer;
@PostConstruct
public void init() throws Exception {
couchbaseContainer = AbstractSPDataTestConfig.couchbaseContainer;
BucketSettings settings = DefaultBucketSettings.builder()
.enableFlush(true).name("default").quota(100).replicas(0).type(BucketType.COUCHBASE).build();
settings = couchbaseCluster().clusterManager(clusterUser, clusterPassword).insertBucket(settings);
couchbaseContainer.callCouchbaseRestAPI("/settings/indexes", "indexerThreads=0&logLevel=info&maxRollbackPoints=5&storageMode=memory_optimized", "Administrator", "password");
waitForContainer();
}
public void waitForContainer(){
CouchbaseWaitStrategy s = new CouchbaseWaitStrategy();
s.withBasicCredentials(clusterUser, clusterPassword);
s.waitUntilReady(couchbaseContainer);
}
@Override
@Bean
public CouchbaseEnvironment couchbaseEnvironment() {
return couchbaseContainer.getCouchbaseEnvironnement();
}
@Override
@Bean
public Cluster couchbaseCluster() throws Exception {
return couchbaseContainer.geCouchbaseCluster();
}
@Override
@Bean
public ClusterInfo couchbaseClusterInfo() throws Exception {
Cluster cc = couchbaseCluster();
ClusterManager manager = cc.clusterManager(clusterUser, clusterPassword);
return manager.info();
}
@Override
@Bean
public Bucket couchbaseClient() throws Exception {
return couchbaseContainer.geCouchbaseCluster().openBucket("default");
}
}
}
Once we have this abstract test case, all we have to do next is create a class that extends it and start writing tests! Here we can inject services from our application as well as a lower level bucket. What you see in this test is first a call to an importer service that creates documents. Then we create an Index on the default bucket and test a query on it.
public class GitTalentGHImportTests extends AbstractSPDataTestConfig {
@Autowired
private GithubImportService githubImportService;
@Autowired
private Bucket bucket;
@Test
public void importDevAdvocateTeam(){
githubImportService.importOneDeveloper("ldoguin");
N1qlQueryResult result = bucket.query(N1qlQuery.simple("CREATE PRIMARY INDEX ON default"));
N1qlQuery query = N1qlQuery.simple("SELECT * FROM default WHERE developerInfo.username = 'ldoguin'");
result = bucket.query(query);
N1qlQueryRow row = result.rows().next();
Assert.assertNotNull(row);
}
}
As you can see, once the Abstract test case is created, the amount of code is really minimal and corresponds exactly to what you want to test.
Published at DZone with permission of Laurent Doguin, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments