How to test Hibernate criteria queries without using any database?

痞子三分冷 提交于 2019-12-01 14:30:18

问题


I'm developing a Java application with lots of complex Hibernate criteria queries. I would like to test these criteria to make sure they are selecting the right, and only the right, objects. One approach to this, of course, is to set up an in-memory database (e.g. HSQL) and, in each test, make a round trip to that database using the criteria and then assert that the query results match my expectations.

But I'm looking for a simpler solution, since Hibernate criteria are just a special kind of logical predicates about Java objects. Thus they could, in theory, be tested without accessing any database at all. For example, assuming that there is a entity called Cat:

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

I would like to do something like this, to create criteria queries:

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)))

The criteria could be used in production code like this:

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

Is there any Java library that makes this kind of test possible?


回答1:


You could use a mocking framework like Mockito to mock all relevant Hibernate classes and define expected behavior of these mocks.

Sounds like a lot of code, but since the Hibernate Criteria API is a fluent interface, all methods of Criteria return a new instance Criteria. So defining the mock behavior which is common to all tests is simple. Here is an example using 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);
}

All methods of the Criteria mock return the mock again.

In a concrete test you would then use a ArgumentCaptor and verify statements to investigate what happened to the mocked 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());
}

The problem I see with this kind of unitests is that they impose a lot of expectations about the class under test. If you think of serviceUnderTest as a blackbox, you can't know how it retrieves the cat object by name. It could also use a LIKE criterion or even 'IN' instead of =, further it could use the Example criterion. Or it could execute a native SQL query.



来源:https://stackoverflow.com/questions/9212131/how-to-test-hibernate-criteria-queries-without-using-any-database

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!