GCP Datastore : Integration Testing using Spring Boot

In this article, I'll show you how you can create an in-memory datastore used by your tests.

Configuring a Datastore Profile Testing

Spring 3.1 introduced the bean definition profiles which can help us implementing an appropriate configuration for testing datastore staff. So, we'll have a specific profile for the GCP Datastore environment.

package com.vedrax.company.config;
import com.vedrax.tester.ITester;
import com.vedrax.tester.impl.GCPDatastoreTester;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
/**
*
* @author remypenchenat
*/
@Configuration
@ComponentScan(basePackages = {"com.vedrax"})
@Profile("datastoreTest")
public class ServiceTestConfig {
@Bean(name = "datastoreTester")
public ITester datastoreTester() {
ITester tester = new GCPDatastoreTester();
return tester;
}
}
Basically, the GCPDatastoreTester() class helps us saving data to the local in-memory datastore.

Custom Annotation

In order to support the population of test data for each test case, we will create an annotation in which you can add the JSON file name as argument. In the test execution listener, we'll check for the existence of the annotation and load the data accordingly.

package com.vedrax.tester;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author remypenchenat
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSets {
String json() default "";
}
We'll use the annotation as follow :

@DataSets(json = "myTestingDataSet.json")
@Test
public void testCreateCompany() {
//something to test
}


Implementing Custom TestExecutionListener


The TestExecutionListener interface in the spring-test module defines a listener API for intercepting the events of the test case execution. 

Process :

  1. We check for the existence of the annotation 
  2. we will create an instance of LocalServiceTestHelper with LocalDatastoreServiceTestConfig which handles all the GCP environment setup before testing. 
  3. Load and insert data using the setDataset() method.


package com.vedrax.company.config;
import com.vedrax.tester.DataSets;
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;
import com.vedrax.tester.ITester;
/**
*
* @author remypenchenat
*/
public class ServiceTestExecutionListener implements TestExecutionListener {
private final LocalServiceTestHelper helper
= new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());
private ITester datastoreTester;
@Override
public void afterTestMethod(TestContext testCtx) throws Exception {
helper.tearDown();
}
@Override
public void beforeTestMethod(TestContext testCtx) throws Exception {
DataSets datasetAnnotation = testCtx.getTestMethod().getAnnotation(DataSets.class);
helper.setUp();
if (datasetAnnotation == null) {
return;
}
String datasetName = datasetAnnotation.json();
if (!datasetName.equals("")) {
//see configuration file for the bean
datastoreTester = (ITester) testCtx.getApplicationContext().getBean("datastoreTester");
datastoreTester.setDataSet(datasetName);
}
}
}


As you can see above we override only the beforeTestMethod() and afterTestMethod() in which we call also the setUp() and tearDown() methods.


How To Use

package com.vedrax.company.service;
import com.vedrax.company.config.ServiceTestConfig;
import com.vedrax.company.config.ServiceTestExecutionListener;
import com.vedrax.company.domain.Company;
import com.vedrax.tester.DataSets;
import java.util.Optional;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
/**
*
* @author remypenchenat
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {ServiceTestConfig.class})
@TestExecutionListeners({ServiceTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class})
@ActiveProfiles("datastoreTest")
public class CompanyServiceTest {
@Autowired
private CompanyService companyService;
@DataSets(json = "companyTest1.json")
@Test
public void testCreateCompany() {
Optional<Company> companyOpt = companyService.getCompanyByDomainName("vedrax.com");
assertTrue(companyOpt.isPresent());
}
}
Because we use our own test execution listener, all default listeners are overridden. In order for the DI to work properly we've also added DependencyInjectionTestExecutionListener.

Comments

Popular posts from this blog

Spring JPA : Using Specification with Projection

Chip input using Reactive Form