Mocking/stubbing stuff near the "bottom of the pile"...

2,432 views
Skip to first unread message

Russell Bateman

unread,
Mar 16, 2012, 12:58:37 PM3/16/12
to moc...@googlegroups.com
I know the caveat about mocking/stubbing stuff one doesn't own, in this case, Hibernate. And yet, can I get some guidance on how to write JUnit tests for my DAO layer? I've already got some working ones for the (business) layer just above it, but I'm still struggling.

Here, I'm trying to make it so that, in the method I call in the class under test, the statement

    sessionFactory.getCurrectSession().save(object)


will do nothing. But, things go south with:

org.mockito.exceptions.misusing.UnfinishedStubbingException:
Unfinished stubbing detected here:
-> at com.hp.web.user.entity.LanguageRepositoryTest.testCreate(LanguageRepositoryTest.java:59)

E.g. thenReturn() may be missing.
Examples of correct stubbing:
    when(mock.isOk()).thenReturn(true);
    when(mock.isOk()).thenThrow(exception);
    doThrow(exception).when(mock).someVoidMethod();
Hints:
 1. missing thenReturn() 
(Uh, I don't think I wanted to return anything, I'm stubbing, no?)
 2. you are trying to stub a final method, you naughty developer!
(Not naughty, just stupid for now!)

    at com.hp.web.user.entity.LanguageDaoTest.testCreate(LanguageDaoTest.java:59)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    ...


Here's the relevant code. I'm embarrassed to dump so much in here; it's because I might not really know what question I'm trying to ask.


public class LanguageDaoTest
{
    private LanguageDao dao;

    @Mock
    private SessionFactory sessionFactory;      // (inject into BaseDao)

    @Before
    public void setup()
    {
        MockitoAnnotations.initMocks( this );

        this.dao = new LanguageDao();
        this.
dao.setSessionFactory( this.sessionFactory );
    }

    @Test
    public void testCreate()
    {
        Language ENGLISH = mock( Language.class );  // (stabbing in the dark)

        // (hoping this will allow dao.create() to run without trouble...)
        doNothing().when( sessionFactory.getCurrentSession() ).save( any() );

        // I'm sort of confident about where I want to go from here...
        Language result = this.dao.create( ENGLISH );

        verify( this.
dao ).create( result );
        assertNotNull( result );
        assertTrue( checkLanguageResult( ENGLISH, result ) );
    }
    ...


@Transactional
@Repository( "languageDao" )
public class LanguageDao extends BaseDao
{
    @Transactional( readOnly = false, propagation = Propagation.MANDATORY )
    public Language create( Language language )
    {
        sessionFactory.getCurrentSession().save( language );
        return language;
    }
    ...

public class BaseDao
{
    @Autowired
    public SessionFactory sessionFactory;

    @Autowired
    public HibernateTemplate hibernateTemplate;

    public BaseDao() { super(); }

    /** This is used by JUnit testing only. */
    public void setSessionFactory( SessionFactory factory )
    {
        this.sessionFactory = factory;
    }
}


I greatly appreciate all the help I've got from this group so far. Based on one or two responses last week, I was able to work out successfully the trouble with my Mockito-based tests for the business layer just above this one.

Profuse thanks and best regards,

Russ Bateman

jordi

unread,
Mar 16, 2012, 1:30:51 PM3/16/12
to moc...@googlegroups.com
Hey Russ,

I think that you want to test that your save method is called. If that's the case, I would rewrite your test as follows:

 @Mock SessionFactory sessionFactory;
 @Mock Session session;
  
  @Test
  public void create_shouldCallSave() {
     when(sessionFactory.getCurrenSession()).thenReturn(session);
    
      // your method under test
    
      verify(session).save(anyString());
  }

But if you really need to use doNothing, this will work too:

  doNothing().when(session).save(anyString());

hth,
jordi

--
You received this message because you are subscribed to the Google Groups "mockito" group.
To post to this group, send email to moc...@googlegroups.com.
To unsubscribe from this group, send email to mockito+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/mockito?hl=en.

Russell Bateman

unread,
Mar 16, 2012, 2:02:48 PM3/16/12
to moc...@googlegroups.com, jordi
Thank you very much for these suggestions. I may still be doing something wrong. Both these fail for me, and for different reasons.

Eclipse insists that I cast
session thus, which is something nasty I've read about out there in Google land and never found an answer to. I don't know the difference between org.hibernate.Session, which I'm using in my code, and the "classic" one, which I didn't even know about until I began trying to work with Hibernate and Mockito in the same breath:

@Mock SessionFactory sessionFactory;
@Mock Session        session;           ----added this

@Test
public void create_should_call_save()
{
    when( sessionFactory.getCurrentSession() ).thenReturn(
( org.hibernate.classic.Session ) session );


    Language result = this.dao.create( ENGLISH );

    verify( this.session ).save( any() );

    assertNotNull( result );
    assertTrue( checkLanguageResult( ENGLISH, result ) );
}

I get this result:

java.lang.ClassCastException: org.hibernate.Session$$EnhancerByMockitoWithCGLIB$$a94debe1 cannot be cast to org.hibernate.classic.Session
    at com.hp.web.user.entity.LanguageRepositoryTest.create_should_call_save(LanguageRepositoryTest.java:61)

    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    ...

Anyway, no, I don't think I do wish to call session.save() since I'm trying to eliminate Hibernate from the equation by mocking/stubbing/shutting it up. (If I'm mistaken in the way I look at this, feel free to tell me.)

Next, not calling it, yields the same complaint I wrote for:

@Test
public void create_dont_call_save()

{
    Language ENGLISH = mock( Language.class );

    doNothing().when( session ).save( any() );


    Language result = this.dao.create( ENGLISH );

    verify( this.dao).create( ENGLISH );

    assertNotNull( result );
    assertTrue( checkLanguageResult( ENGLISH, result ) );
}


yields:

org.mockito.exceptions.misusing.UnfinishedStubbingException:
Unfinished stubbing detected here:
-> at com.hp.web.user.entity.LanguageRepositoryTest.create_should_call_save(LanguageRepositoryTest.java:61)


E.g. thenReturn() may be missing.
Examples of correct stubbing:
    when(mock.isOk()).thenReturn(true);
    when(mock.isOk()).thenThrow(exception);
    doThrow(exception).when(mock).someVoidMethod();
Hints:
 1. missing thenReturn()
 2. you are trying to stub a final method, you naughty developer!

    etc.

Russ

jordi

unread,
Mar 16, 2012, 2:32:46 PM3/16/12
to Russell Bateman, moc...@googlegroups.com
On Fri, Mar 16, 2012 at 7:02 PM, Russell Bateman <ru...@windofkeltia.com> wrote:
Thank you very much for these suggestions. I may still be doing something wrong. Both these fail for me, and for different reasons.

Eclipse insists that I cast
session thus, which is something nasty I've read about out there in Google land and never found an answer to. I don't know the difference between org.hibernate.Session, which I'm using in my code, and the "classic" one, which I didn't even know about until I began trying to work with Hibernate and Mockito in the same breath:

@Mock SessionFactory sessionFactory;
@Mock Session        session;           ----added this

@Test
public void create_should_call_save()
{
    when( sessionFactory.getCurrentSession() ).thenReturn(
( org.hibernate.classic.Session ) session );


    Language result = this.dao.create( ENGLISH );

    verify( this.session ).save( any() );

    assertNotNull( result );
    assertTrue( checkLanguageResult( ENGLISH, result ) );
}

I get this result:

java.lang.ClassCastException: org.hibernate.Session$$EnhancerByMockitoWithCGLIB$$a94debe1 cannot be cast to org.hibernate.classic.Session
    at com.hp.web.user.entity.LanguageRepositoryTest.create_should_call_save(LanguageRepositoryTest.java:61)

    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    ...


Anyway, no, I don't think I do wish to call session.save() since I'm trying to eliminate Hibernate from the equation by mocking/stubbing/shutting it up. (If I'm mistaken in the way I look at this, feel free to tell me.)

Classic Session is legacy from Hibernate 2. 

I suspect you're using org.hibernate.Session in your imports, did you tried to import org.hibernate.classic.Session directly?
 
Next, not calling it, yields the same complaint I wrote for:

@Test
public void create_dont_call_save()

{
    Language ENGLISH = mock( Language.class );

    doNothing().when( session ).save( any() );


    Language result = this.dao.create( ENGLISH );

    verify( this.dao).create( ENGLISH );

    assertNotNull( result );
    assertTrue( checkLanguageResult( ENGLISH, result ) );
}


yields:

org.mockito.exceptions.misusing.UnfinishedStubbingException:
Unfinished stubbing detected here:
-> at com.hp.web.user.entity.LanguageRepositoryTest.create_should_call_save(LanguageRepositoryTest.java:61)


E.g. thenReturn() may be missing.
Examples of correct stubbing:
    when(mock.isOk()).thenReturn(true);
    when(mock.isOk()).thenThrow(exception);
    doThrow(exception).when(mock).someVoidMethod();
Hints:
 1. missing thenReturn()
 2. you are trying to stub a final method, you naughty developer!

    etc.

if your mock does nothing as it seems, that's exactly what it does Mockito by default! Mockito uses intelligent defaults for your mocks. Besides that, from doNothing javadoc:

Use doNothing() for setting void methods to do nothing. Beware that void methods on mocks do nothing by default! However, there are rare situations when doNothing() comes handy... check the Examples [1]
 
I think that you're save to remove that stubbing line and everything will be fine

Russ 

jordi

Mattias Jiderhamn

unread,
Mar 16, 2012, 2:50:46 PM3/16/12
to moc...@googlegroups.com
Well, my recommendation is: don't mock.
Instead use an in memory database such as Hypersonic / hsqldb [1], and
test the DAOs against an actual database.

In our case this has been very helpful, since Hibernate is full of bugs
and limitations when you move out of the non-trivial. By having
Hibernate actually run the SQL statement against a database, rather than
just verify that the Hibernate session has been passed some criteria or
query, we are able to catch the bugs up front. This also helps catch
problems with your mappings/annotations.

Sure, there may be variations between hsqldb and your production
database, both in SQL support and Hibernate translation, so this isn't
*istead of* integration tests, but those cases are rare in comparison to
the cases where hsqldb really is helpful.

1: http://hsqldb.org/

</Mattias>

----- Original Message -----
Subject: [mockito] Mocking/stubbing stuff near the "bottom of the pile"...
Date: Fri, 16 Mar 2012 10:58:37 -0600
From: Russell Bateman <ru...@windofkeltia.com>

I know the caveat about mocking/stubbing stuff one doesn't own, in this
case, Hibernate. And yet, can I get some guidance on how to write JUnit

tests for my DAO layer? ...

Brice Dutheil

unread,
Mar 16, 2012, 7:58:58 PM3/16/12
to moc...@googlegroups.com
+1000 to Mattias, this is definitely the way to go.
 
I know the caveat about mocking/stubbing stuff one doesn't own
You are fully experimenting this caveat right now !

Seriously, don't mock types you don't own. This might sound like repetition, but many more intelligent people than me tried and got their finger burned.

Finally about the UnfinishedStubbingException, you are using Mockito verify syntax on a real object, not on the mock:

--> verify( this.dao).create( ENGLISH );

dao is no a mock.


Hope that helps,

-- Brice



--
You received this message because you are subscribed to the Google Groups "mockito" group.
To post to this group, send email to moc...@googlegroups.com.
To unsubscribe from this group, send email to mockito+unsubscribe@googlegroups.com.

David Wallace

unread,
Mar 16, 2012, 8:32:58 PM3/16/12
to moc...@googlegroups.com
Absolutely. Your DAO implementation should be so simple that there's no
real logic in it; just delegation off to the database. You then don't use
unit tests, since what you really want to test is that you're calling the
database correctly, using valid SQL (or HQL or Hibernate Criterias,
whatever). So you test it with Hibernate, and a real database; but no
mocks.

These are effectively integration tests, because they're testing that your
DAO integrates correctly to Hibernate. To get your integration tests to be
efficient, you really want an in-memory database, as Mattias has suggested.
My favourite is H2 (http://www.h2database.com/).

Hope this helps,
David.

1: http://hsqldb.org/

</Mattias>

--

You received this message because you are subscribed to the Google Groups
"mockito" group.
To post to this group, send email to moc...@googlegroups.com.
To unsubscribe from this group, send email to

mockito+u...@googlegroups.com.

jordi

unread,
Mar 17, 2012, 5:43:25 AM3/17/12
to moc...@googlegroups.com

yeah, that would be my recommendation too, to use hsqldb and run directly with hibernate.

Since touching a real database is not a unit test anymore, personally I prefer not to do just "hibernate tests" if not necessary. They used to be included in larger integration test suites with webdriver or similar, when we're verifying whole features and not just a part of the system.

To unsubscribe from this group, send email to mockito+unsubscribe@googlegroups.com.

For more options, visit this group at http://groups.google.com/group/mockito?hl=en.

--
You received this message because you are subscribed to the Google Groups "mockito" group.
To post to this group, send email to moc...@googlegroups.com.
To unsubscribe from this group, send email to mockito+unsubscribe@googlegroups.com.

Russell Bateman

unread,
Mar 17, 2012, 9:58:50 AM3/17/12
to moc...@googlegroups.com
Thanks to the entire group for enduring my question and for their help. The advice will be heeded!

Best regards,

Russ Bateman
To unsubscribe from this group, send email to mockito+u...@googlegroups.com.

For more options, visit this group at http://groups.google.com/group/mockito?hl=en.

--
You received this message because you are subscribed to the Google Groups "mockito" group.
To post to this group, send email to moc...@googlegroups.com.
To unsubscribe from this group, send email to mockito+u...@googlegroups.com.

For more options, visit this group at http://groups.google.com/group/mockito?hl=en.

--
You received this message because you are subscribed to the Google Groups "mockito" group.
To post to this group, send email to moc...@googlegroups.com.
To unsubscribe from this group, send email to mockito+u...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages