我有一个JAVA休息服务,它与数据库交互,进行一些操作并返回数据。
我正在尝试为这些API编写测试用例。
我正在尝试使用以下链接来实现此目的。
这里,对数据库的调用被抑制,dto被模拟为虚拟值。
是否存在一种替代方法,我们实际上可以在与数据库通信时运行查询(内存数据库可能是?)
任何代码示例或参考都会有很大的帮助。
答案 0 :(得分:0)
HSQLDB是我熟悉的内存数据库中的一个。这里的示例显示了与hibernate和JPA一起使用的HSQLDB。 http://uaihebert.com/tdd-with-hsqldb-jpa-and-hibernate/。
但是,我认为在你的情况下,为什么你更愿意连接到内存数据库而不是模拟数据库? 它归结为您尝试实现的测试单元/集成的范围。 你在试图测试其余层中的manupilation逻辑是什么?嘲笑就足够了。 您是否正在尝试测试其余部分如何处理数据访问行为(例如数据库错误处理等),而不是在内存中可能稍好一些。 你正在测试的东西是依赖于数据设置/测试数据设置,在这种情况下内存db可能更接近,因为你可以使用相同/类似的sql创建来测试inmemory db。
答案 1 :(得分:0)
对于一个纯粹的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 {