Mock a JPA CriteriaBuilder with Mockito
Asked Answered
O

2

6

I have a particularly nasty JMock checking() block for a JPA query that I want to migrate to Mockito:

Mockery jMock = new Mockery();
final EntityManager fakeEntityManager = jMock.mock(EntityManager.class);
final CriteriaBuilder fakeCriteriaBuilder = jMock.mock(CriteriaBuilder.class);
final CriteriaQuery<String> fakeCriteriaQuery = jMock.mock(CriteriaQuery.class);
jMock.checking(new Expectations() {{
    oneOf(fakeEntityManager).getCriteriaBuilder(); will(returnValue(fakeCriteriaBuilder));
    oneOf(fakeCriteriaBuilder).createQuery(String.class); will(returnValue(fakeCriteriaQuery));
    oneOf(fakeCriteriaQuery).from(Archiveusergrouplicences.class);
    oneOf(fakeCriteriaQuery).select(with(any(Selection.class)));
    oneOf(fakeCriteriaBuilder).isNotNull(with(any(Expression.class)));
    oneOf(fakeCriteriaQuery).where(with(any(Expression.class)));
    oneOf(fakeEntityManager).createQuery(fakeCriteriaQuery);
    // Return an empty resultset
}});

The code being tested looks like this:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<String> criteria = builder.createQuery(String.class);

Root<Archiveusergrouplicences> institution = criteria.from(Archiveusergrouplicences.class);
criteria.select(institution.get(Archiveusergrouplicences_.usergroupid));    
criteria.where(builder.isNotNull(institution.get(Archiveusergrouplicences_.usergroupid)));

List<String> result = entityManager.createQuery(criteria).getResultList();

I've found this question on mocking builders, which goes some way to solving the CriteriaBuilder part of the mock; but my main problem is with using mocked objects as the .thenReturn() value of another mock - Mockito does not seem to allow that. For example, for the line:

CriteriaQuery<String> criteria = builder.createQuery(String.class);

I want to return the mock CriteriaQuery object, like this:

CriteriaQuery<String> fakeCriteriaQuery = mock(CriteriaQuery.class, RETURNS_DEEP_STUBS);
when(entityManager.createQuery(anyString())).thenReturn(fakeCriteriaQuery);

This throws a syntax error:

The method thenReturn(Query) in the type OngoingStubbing is not applicable for the arguments (CriteriaQuery)

How might I go about testing this code, or improving it to make it more testable?

Oxymoron answered 8/2, 2012 at 10:1 Comment(3)
The question I'd ask myself is if mocking the whole ORM is really worth the trouble or whether it might be easier to just use e.g. an in-memory database for testing. dbunit.org proved to be useful for this, too, in the past.Imbecilic
All the other tests are functional (using a test database and something similar to dbunit). The purpose of the test I am migrating seems to assert that when an unexpected exception occurs, the DAO returns a NoResultsException to the caller. I think the exception depends on scenarios like the database going down during a query - or in the case being tested: an NPE being returned by the JPA framework.Oxymoron
Delving further into the test, there might be a better way to write it. Using a func test and deleting the table before executing the query might be a better approach than forcing an NPE.Oxymoron
O
3

It turns out you can return mocks from other mocks - so long as you set the right arguments! I was attempting to assert:

when(entityManager.createQuery(anyString())).thenReturn(fakeCriteriaQuery);

When what I actually wanted was to pass in a Class:

when(fakeCriteriaBuilder.createQuery(String.class)).thenReturn(fakeCriteriaQuery);

The error was Mockito's cryptic way of telling me I had screwed up my expectation.

However, I may look into rewriting this test rather than translating what was originally written. As some have pointed out; it's often better to avoid mocking libraries in this way, and the condition being checked for is rather vague.

Oxymoron answered 8/2, 2012 at 11:12 Comment(1)
Mockito has a default answer called, "RETURNS_DEEP_STUBS" though it's not really useful for builder API. Also mock returning a mock is like a test smell, your are coupling too much your Unit Test with the method implentation, it's whitebox testing! I strongly advise you to think about integrations test, with H2 for example. Antonio Goncalves a JPA's JSR expert member wrote an interesting articles on this matter agoncal.wordpress.com/2012/01/16/…Birdie
P
0

Like you jMock CriteriaQuery

final CriteriaQuery<String> fakeCriteriaQuery = jMock.mock(CriteriaQuery.class);

You need to mockito CriteriaQuery too

final CriteriaQuery<String> fakeCriteriaQuery = mock(CriteriaQuery.class);
Pneumonoultramicroscopicsilicovolcanoconiosis answered 8/2, 2012 at 10:14 Comment(4)
A small reminder from mocking community : Don't mock types you don't own! Write integration tests instead !Birdie
@Brice: Thanks for this pointer. As I indicated in my comment to the question itself, I felt like this was a bad idea. Now, I have got some reference to support this feeling. Thanks again!Imbecilic
Thanks for your answer, but I'm already mocking CriteriaQuery (see my code). This particular test is an edge-case for an otherwise full suite of integration tests.Oxymoron
Yeah if you think it's not possible, then you really should think to write integration tests. If it's legacy code you cannot use either in integration test, then use indirection in your code, like wrappers. But my advice is to stick to that line, otherwise it might hit you hard later !Birdie

© 2022 - 2024 — McMap. All rights reserved.