Millisecond-Startup Integration Testing Framework with Mock Spring and Mock Database
The article presents a millisecond‑startup integration‑testing framework that replaces costly Spring beans and database access with a mock container and mock DAO layer, enabling tests to launch in 3‑10 seconds, use real‑world data safely, and maintain fast, isolated, high‑quality test suites.
This article introduces a millisecond‑startup integration testing framework that enables fast modification and enrichment of test cases, turning test cases into a product of the testing process.
Background : Traditional unit tests cover only limited scopes (utility classes, static methods) and cannot handle the complex layered architecture of DDD (anti‑corruption, domain, service, application layers). Integration tests are slower, especially as the project grows, leading developers to avoid writing tests and rely on manual tools like Postman, which hampers regression after refactoring.
The execution speed of tests is crucial; if an application can start within 10 seconds, developers are more likely to write comprehensive tests.
Solution Overview :
Reduce integration test startup time to the same order of magnitude as unit tests.
Mock time‑consuming beans (RPC, cache, database) to accelerate startup.
Two approaches are discussed:
Use @Primary annotation to disable initialization of costly beans.
Implement a mock Spring container that automatically loads mock beans and constructs the dependency tree on demand.
The second approach is detailed with code snippets.
Mock Spring Container Implementation :
// Determine scan path, only @Component‑annotated classes become Beans
Predicate<Class<?>> classFilter = clazz -> !clazz.getSimpleName().endsWith("Test");
Set<Class<?>> beanClasses = ClassScanUtil.scanPackages(
classFilter,
"com.nbf.gateway",
);Bean creation logic:
protected <T> T getBeanObject(Class<T> requiredType) {
Set<Class<?>> beanClassList = implClassMap.get(requiredType);
int size = CollectionUtils.size(beanClassList);
Class<?> beanClass;
if (size > 1) {
throw new BusinessException(CommonErrorCode.UNKNOWN_EXCEPTION, requiredType.getName() + " contains multiple implementations");
} else if (size == 1) {
beanClass = beanClassList.iterator().next();
} else {
beanClass = requiredType;
}
Constructor<?> constructor = ListUtils.firstElementOf(beanClass.getConstructors());
if (constructor == null) {
throw new BusinessException(new Exception("class: " + beanClass.getName() + " constructor is null."));
}
Class<?>[] classes = constructor.getParameterTypes();
Object[] params = new Object[classes.length];
for (int i = 0; i < classes.length; ++i) {
params[i] = this.getBean(classes[i]);
}
T bean;
try {
bean = (T)constructor.newInstance(params);
} catch (Exception e) {
throw new BusinessException(e);
}
this.processMemberBean(bean);
for (Method method : beanClass.getDeclaredMethods()) {
if (method.getAnnotation(PostConstruct.class) != null) {
try { method.invoke(bean); } catch (Exception e) { throw new BusinessException(e); }
}
}
return bean;
}Mock Spring container is used in tests via MockApplicationContext.getBeanOfType(...) :
public class GroupVersionRepositoryUnitTest {
private static final GroupTunnel groupTunnel = MockApplicationContext.getBeanOfType(GroupTunnel.class);
}Mock Database :
The simplest idea is to replace DAO operations with a HashMap , but this requires manual translation and is error‑prone. Instead, a mock DAO layer mimics MyBatis Example / Criteria structures.
Key interfaces:
public interface SqlCriteria<Criterion> { List<Criterion> getCriteria(); }
public interface SqlCriterion { String getCondition(); Object getValue(); Object getSecondValue(); }
public interface SqlParam<Criteria> {
boolean isPage(); Integer getPageIndex(); Integer getPageSize();
String getOrderByClause(); List<Criteria> getOredCriteria();
}Conversion from MyBatis Param to MockExample :
protected MockExample<DO> convert(Param param) {
MockExample<DO> example = new MockExample<>();
boolean first = true;
SqlParam<Criteria> sqlParam = getSqlParam(param);
for (Criteria criteria : sqlParam.getOredCriteria()) {
MockCriteria<DO> mockCriteria = first ? example.createCriteria() : example.or();
first = false;
SqlCriteria<Criterion> sqlCriteria = getSqlCriteria(criteria);
for (Criterion criterion : sqlCriteria.getCriteria()) {
SqlCriterion sqlCriterion = getSqlCriterion(criterion);
String condition = sqlCriterion.getCondition();
int idx = condition.indexOf(NbfSymbolConstants.SPACE);
String property = NbfStringUtils.underLineToCamel(condition.substring(0, idx).trim());
String getterMethod = "get" + StringUtils.capitalize(property);
String operator = condition.substring(idx + 1).trim();
List<Object> valueList = new ArrayList<>();
if (sqlCriterion.getValue() != null) valueList.add(sqlCriterion.getValue());
if (sqlCriterion.getSecondValue() != null) valueList.add(sqlCriterion.getSecondValue());
Function<DO, Object> getter = obj -> {
try {
Method m = getDoClass().getDeclaredMethod(getterMethod);
return m.invoke(obj);
} catch (Exception e) { throw new BusinessException(e); }
};
OperatorEnum op = OperatorEnum.of(operator);
mockCriteria.and(op, getter, valueList);
}
}
if (sqlParam.isPage()) example.setPagination(sqlParam.getPageIndex(), sqlParam.getPageSize());
if (StringUtils.isNotBlank(sqlParam.getOrderByClause())) example.setOrderByClause(sqlParam.getOrderByClause());
return example;
}Abstract mock DAO implementation example:
public abstract class AbstractMockDaoImpl<DO, Param, Criteria, Criterion>
extends AbstractMockTunnelImpl<DO, Param, Criteria, Criterion> {
public long countByQuery(Param param) {
MockExample<DO> example = this.convert(param);
return MockTunnelUtil.countByExample(this, example);
}
public int deleteByQuery(Param param) {
MockExample<DO> example = this.convert(param);
return MockTunnelUtil.deleteByExample(this, example);
}
}Data Preparation (Mock Data Generation) :
Test data can be generated from real database rows using TableLoadUtil.load , which reads a data file and a metadata file describing column order, then populates DO objects via reflection.
public class TableLoadUtil {
/** Load data according to metadata */
public static
List
load(String fileName, String metadata, Class
clazz);
}Base test class for data setup/cleanup:
public class DataPrepareBaseOnTable {
@BeforeClass
public static void prepare() {
cleanUp();
initData(BackendServiceConfigDO.class, MockBackendServiceConfigMapperImpl.getInstance()::insert);
}
@AfterClass
public static void cleanUp() {
MockBackendServiceConfigMapperImpl.getInstance().getCache().clear();
}
public static
void initData(Class
clazz, Consumer
insertMethod) {
String objName = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - 2);
List
list = TableLoadUtil.load(
"table/" + objName + "/" + objName + ".txt",
"table/Metadata/" + objName + ".txt",
clazz);
NbfListUtils.forEach(list, insertMethod);
}
}Advantages of this approach:
Test execution is fast; most tests start within 3‑10 seconds.
Data quality is high because real online data can be used (with necessary desensitization).
Test data is isolated, version‑controlled, and does not pollute the database.
No need to start a full Spring container; the mock container suffices.
Mock Middleware (RPC, Redis) is also implemented using simple HashMap ‑based mocks, with JSON files storing request‑response mappings.
Conclusion : The presented framework enables second‑level startup for integration tests, simplifies test case maintenance, and allows seamless use of online data for high‑quality testing and rapid debugging of production issues.
DaTaobao Tech
Official account of DaTaobao Technology
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.