如何在不使用任何数据库的情况下测试Hibernate条件查询?

问题描述:

我正在开发一个包含大量复杂Hibernate条件查询的Java应用程序。我想测试这些标准,以确保他们选择正确的,只有正确的对象。其中一种方法当然是建立一个内存数据库(例如HSQL),并且在每个测试中,使用标准往返该数据库,然后断言查询结果与我的期望相符。如何在不使用任何数据库的情况下测试Hibernate条件查询?

但我正在寻找一个更简单的解决方案,因为Hibernate标准只是一种关于Java对象的特殊类型的逻辑谓词。因此,他们理论上可以在没有访问任何数据库的情况下进行测试。例如,假设有一个名为Cat实体:

class Cat { 
    Cat(String name, Integer age){ 
     this.name = name; 
     this.age = age; 
    } 
    ... 
} 

我愿做这样的事情,创造条件查询:

InMemoryCriteria criteria = InMemoryCriteria.forClass(Cat.class) 
    .add(Restrictions.like("name", "Fritz%")) 
    .add(Restrictions.or(
     Restrictions.eq("age", new Integer(0)), 
     Restrictions.isNull("age"))) 

assertTrue(criteria.apply(new Cat("Foo", 0))) 
assertTrue(criteria.apply(new Cat("Fritz Lang", 12))) 
assertFalse(criteria.apply(new Cat("Foo", 12))) 

的标准可能在生产代码中使用这样的:

criteria.getExecutableCriteria(session); //similar to DetachedCriteria 

是否有任何Java库,使得这种测试可能的?

您可以使用像Mockito这样的模拟框架来模拟所有相关的Hibernate类并定义这些模拟的预期行为。

听起来像一个大量的代码,但因为Hibernate的标准API是一个流畅的界面中,Criteria所有方法返回一个新的实例Criteria。因此,定义所有测试共同的模拟行为很简单。 下面是使用的Mockito

@Mock 
private SessionFactory sessionFactory; 

@Mock 
Session session; 

@Mock 
Criteria criteria; 

CatDao serviceUnderTest; 

@Before 
public void before() 
{ 
    reset(sessionFactory, session, criteria); 
    when(sessionFactory.getCurrentSession()).thenReturn(session); 
    when(session.createCriteria(Cat.class)).thenReturn(criteria); 
    when(criteria.setFetchMode(anyString(), (FetchMode) anyObject())).thenReturn(criteria); 
    when(criteria.setFirstResult(anyInt())).thenReturn(criteria); 
    when(criteria.setMaxResults(anyInt())).thenReturn(criteria); 
    when(criteria.createAlias(anyString(), anyString())).thenReturn(criteria); 
    when(criteria.add((Criterion) anyObject())).thenReturn(criteria); 

    serviceUnderTest = new CatDao(sessionFactory); 
} 

Criteria模拟返回的所有方法再次模拟的例子。

在具体测试中,您将使用ArgumentCaptorverify语句来调查嘲笑Criteria发生了什么事情。

@Test 
public void testGetCatByName() 
{ 
    ArgumentCaptor<Criterion> captor = ArgumentCaptor.forClass(Criterion.class); 

    serviceUnderTest.getCatByName("Tom"); 

    // ensure a call to criteria.add and record the argument the method call had 
    verify(criteria).add(captor.capture()); 

    Criterion criterion = captor.getValue(); 

    Criterion expectation = Restrictions.eq("name", "Tom"); 

    // toString() because two instances seem never two be equal 
    assertEquals(expectation.toString(), criterion.toString()); 
} 

我用这种unitests的看到的问题是,他们强加了很多关于被测类的预期。如果您认为serviceUnderTest 作为黑匣子,您无法知道它是如何按名称检索cat对象的。它也可以使用LIKE标准,甚至可以使用“IN”而不是=,进一步它可以使用 Example标准。或者它可以执行原生的SQL查询。

+4

谢谢你的回答!但是,正如你指出的那样,这种测试验证类是如何实现的,而不是它应该如何表现。此外,此测试专注于另一项服务,恰好使用这些标准。虽然这当然是一个重要的功能,但它应该相对容易测试(通过正确使用接口来分离类,分离职责等)。在这里,我对测试Criteria对象本身更感兴趣。 – 2012-02-23 15:45:27