对于纯 HashMap 解决方案,类似这样的方法可以工作,但您会失去对 SQL 查询功能的访问权限(除非您也模拟查询)。
public class MockDatabase<T> {
protected Map<Serializable, T> fakeDatabase = Maps.newHashMap();
private final CustomRepository<T,Serializable> repository;
private Validator validator;
public void setValidator(Validator validator) {
this.validator = validator;
}
public static <T extends CustomRepository> T mock(Class<T> classToMock, Validator validator) {
T repository = Mockito.mock(classToMock);
MockDatabase md = new MockDatabase<T>(repository, validator);
return repository;
}
public <ID extends Serializable> MockDatabase(CustomRepository<T, ID> repository, Validator validator){
this.repository = (CustomRepository<T, Serializable>) repository;
this.validator = validator;
reset(repository);
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
fakeDatabase.clear();
return null;
}
}).when(repository).deleteAll();
when(repository.save((T) anyObject())).thenAnswer(new Answer<T>() {
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
return saveOrSaveAndFlush(invocation);
}
});
when(repository.getReference((ID)anyObject())).thenAnswer(new Answer<T>() {
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
return fakeDatabase.get(invocation.getArguments()[0]);
}
});
when(repository.findOne((ID)anyObject())).thenAnswer(new Answer<T>() {
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
return fakeDatabase.get(invocation.getArguments()[0]);
}
});
doAnswer(new Answer<T>() {
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
return fakeDatabase.remove(ReflectionTestUtils.invokeGetterMethod(invocation.getArguments()[0], "getId"));
}
}).when(repository).delete((T)anyObject());
doAnswer(new Answer<ID>() {
@Override
public ID answer(InvocationOnMock invocation) throws Throwable {
fakeDatabase.remove(((ID)invocation.getArguments()[0]));
return null;
}
}).when(repository).delete((ID)anyObject());
when(repository.saveAndFlush((T) anyObject())).thenAnswer(new Answer<T>() {
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
return saveOrSaveAndFlush(invocation);
}
});
when(repository.exists((ID)anyObject())).thenAnswer(new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
return fakeDatabase.containsKey(invocation.getArguments()[0]);
}
});
when(repository.merge(anyObject())).thenAnswer(new Answer<T>() {
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
return (T) invocation.getArguments()[0];
}
});
when(repository.findAll()).thenAnswer(new Answer<List<T>>() {
@Override
public List<T> answer(InvocationOnMock invocation) throws Throwable {
return Lists.newLinkedList(fakeDatabase.values());
}
});
customMethods();
}
private T saveOrSaveAndFlush(InvocationOnMock invocation) throws NoSuchMethodException {
Object[] args = invocation.getArguments();
Serializable id = (Serializable) ReflectionTestUtils.getField(args[0], "id");
if (id == null) {
Class<?> returnType = args[0].getClass().getMethod("getId").getReturnType();
if (returnType.equals(Long.class)) {
id = (Long) new Random().nextLong();
} else if (returnType.equals(Integer.class)) {
id = (Integer) new Random().nextInt();
}
ReflectionTestUtils.setField(args[0], "id", id);
}
Set<ConstraintViolation<T>> validations = validator.validate((T)args[0]);
if (!validations.isEmpty()){
throw new IllegalStateException("Object failed validations (it would also fail on a db): "+validations);
}
for (Method method: args[0].getClass().getDeclaredMethods()){
if (method.isAnnotationPresent(Basic.class)){
Annotation a = method.getAnnotation(Basic.class);
if (!(boolean) AnnotationUtils.getValue(method.getAnnotation(Basic.class), "optional")){
if (ReflectionTestUtils.invokeGetterMethod(args[0], method.getName()) == null){
throw new IllegalStateException(args[0].getClass().getSimpleName()+"."+method.getName() + " returned null, but marked with @Basic(optional=false) - it would also fail on a db: "+validations);
}
}
}
}
fakeDatabase.put(id, (T) args[0]);
return (T) args[0];
}
public void customMethods() {
// override here if you want
}
}
如果你有 @Entity 注释的 POJO,那么说 hibernate 库你可以要求它导出到 HSQLDB 脚本然后使用它。例如,您通过以下方式导出:
Configuration configuration = new Configuration();
try {
classes().forEach(cl -> {
configuration.addAnnotatedClass(cl);
});
configuration.setProperty("hibernate.dialect", HSQLCustomDialect.class.getName());
SchemaExport schemaExport = new SchemaExport(configuration);
schemaExport.setOutputFile("someFileName.sql");
schemaExport.setFormat(false);
schemaExport.setDelimiter(";");
schemaExport.execute(true, false, false, true);
然后您将使用 spring 为您插入该 SQL 脚本:
@ActiveProfiles("test")
@SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)
@SqlGroup({
@Sql(statements = "DROP SCHEMA PUBLIC CASCADE"),
@Sql(scripts = "classpath:yourGeneratedSQL.sql"),
})
公共类 DAOIntegrationTest {