Notes on Mockito Tutorial
Russell Bateman |
Table of Contents |
IntroductionI'm working through a tutorial on Mockito and some others to see if this is the technology we want to use in my group at work. If you have time to work through this, it will show you two important things:
This is a very rough start that will take months to bear fruit. Glossaryreplay is the act of object scaffolding to produce and object. strict mock semantics are when only the methods that are explicitly recorded are accepted as valid. Any method that's not expected causes an exception that fails the test and all the expected methods must be called if the object is to pass verification. loose mock allows any method called during the replay state to be accepted and if there is not special handling set up for the method, a null or zero is returned. All the expected methods must be called if the object is to pass verification.
A stub is an object that is set up to feed the required inputs into
the system under test.
A mock is an object that is used to verify that the system under
test interacts as expected with the object(s) it relies on. happy-path test is a well defined test case using known input that executes without exception and produces expected output. |
First, I loathe the use of wildcards in Java import statements. Here is the complete set for this tutorial:
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.anyString;
At Is there a difference between asking and telling, the inventor of Mockito says, "In practice: If I stub then it is verified for free, so I don't verify. If I verify then I don't care about the return value, so I don't stub. Either way, I don't repeat myself." This posting is not only interesting for the discussion by its author, but also for the discussion in the comments below.
Brice Dutheil says:
@Test(expected = Exception.class)
style. It has the
benifit to remove the clutter.
@Test public testFillingDoesNotRemoveIfNotEnoughInStock()
,
test is redundant with the annotation, you can get rid of it, what we
want is the test to be readable, almost like your native language, I
now use the underscored style as the readability of the test intent
is easier to read than with camel case. PLEASE NOTE that I ONLY
recommand it for tests.
hasInventory(anyString(), anyInt())
, don't need to be
asserted. This line is irrelevant:
assertFalse( mockedWarehouse.hasInventory( TALISKER, 5 )
);
(Get to this table someday.)
stub | when() |
This is from a ReST servlet I've written tests for. The architecture for this particular piece is fairly standard:
StateService (JAX-RS) -> StateBo (business logic) -> StateDao -> State (POJO/bean) -> database
I'm trying to write a unit test for StateBo. There's hardly any business logic in this piece; it was chosen as the simple case to get started on.
Here's the class to be placed under test...
package com.acme.user.bo; import java.util.List; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.acme.user.entity.State; import com.acme.user.entity.StateDao; @Transactional @Service( "stateBo" ) public class StateBo { public static Logger log = Logger.getLogger( StateBo.class ); @Autowired( required = true ) private StateDao stateDao; public State create( State state ) { return this.stateDao.create( state ); } public List< State > findAll() { return this.stateDao.read(); } public State findById( Integer id ) { return this.stateDao.readById( id ); } public List< State > findByPropertyAndValue( String propertyName, String propertyValue ) { return this.stateDao.readByPropertyAndValue( propertyName, propertyValue ); } public void update( State state ) { this.stateDao.update( state ); } public void delete( Integer id ) { State state = findById( id ); this.stateDao.delete( state ); } /** * This injector is just for JUnit/Mockito tests which do not benefit from * proper Spring autowiring. */ public void setDao( StateDao dao ) { this.stateDao = dao; } }
Here's the DAO class that we mock—since we don't actually wish to call down to Hibernate and the database.
package com.acme.user.entity; import java.util.List; import org.apache.log4j.Logger; import org.hibernate.Criteria; import org.hibernate.criterion.MatchMode; import org.hibernate.criterion.Restrictions; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Transactional @Repository( "stateDao" ) public class StateDao extends BaseDao { public static Logger log = Logger.getLogger( StateDao.class ); @Transactional( readOnly = false, propagation = Propagation.MANDATORY ) public State create( State state ) { sessionFactory.getCurrentSession().save( state ); return state; } @SuppressWarnings( "unchecked" ) @Transactional( readOnly = true, propagation = Propagation.MANDATORY ) public List< State > read() { Criteria criteria = sessionFactory.getCurrentSession().createCriteria( State.class ); criteria.isReadOnly(); return criteria.list(); } @Transactional( readOnly = true, propagation = Propagation.MANDATORY ) public State readById( Integer id ) { Criteria criteria = sessionFactory.getCurrentSession().createCriteria( State.class ); criteria.isReadOnly(); criteria.add( Restrictions.idEq( id ) ); return ( State ) criteria.uniqueResult(); } @SuppressWarnings( "unchecked" ) @Transactional( readOnly = true, propagation = Propagation.MANDATORY ) public List< State > readByPropertyAndValue( String propertyName, String propertyValue ) { Criteria criteria = sessionFactory.getCurrentSession().createCriteria( State.class ); criteria.add( Restrictions.ilike( propertyName, propertyValue, MatchMode.EXACT ) ); return criteria.list(); } @Transactional( readOnly = false, propagation = Propagation.MANDATORY ) public void update( State state ) { State stateToModify = readById( state.getId() ); stateToModify.setCode( state.getCode() ); stateToModify.setName( state.getName() ); sessionFactory.getCurrentSession().update( stateToModify ); } @Transactional( readOnly = false, propagation = Propagation.MANDATORY ) public void delete( State state ) { State stateToModify = readById( state.getId() ); sessionFactory.getCurrentSession().delete( stateToModify ); } }
(For completeness, here's the superclass for StateDao.)
package com.acme.user.entity; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.orm.hibernate3.HibernateTemplate; public class BaseDao { @Autowired public SessionFactory sessionFactory; @Autowired public HibernateTemplate hibernateTemplate; public BaseDao() { super(); } }
The POJO at the bottom representing the State table in the database.
package com.acme.user.entity; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement @Entity @Table( name = "state" ) public class State implements Serializable { private static final long serialVersionUID = 7690285996151938000L; private Integer id; private String country; private String code; private String name; public State() {} @Id @GeneratedValue( strategy = GenerationType.SEQUENCE ) @Column( name = "id", unique = true, updatable = false ) public Integer getId() { return id; } public void setId( Integer id ) { this.id = id; } @Column( name = "country", nullable = false, length = 2 ) public String getCountry() { return this.country; } public void setCountry( String country ) { this.country = country; } @Column( name = "code", nullable = false, length = 2 ) public String getCode() { return this.code; } public void setCode( String code ) { this.code = code; } @Column( name = "name", nullable = false, length = 64 ) public String getName() { return this.name; } public void setName( String name ) { this.name = name; } /** * This constructor is just for JUnit/Mockito tests. Providing it forces us to create * an explicit no-argument constructor without which this class fails when consumed by * the ReST servlet-proper. */ public State( Integer id, String country, String code, String name ) { this.id = id; this.country = country; this.code = code; this.name = name; } }
And now, this is the JUnit test mocking the DAO and testing the business logic.
package com.acme.user.bo; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.doNothing; import static org.mockito.Matchers.any; import java.util.LinkedList; import java.util.List; import org.junit.Before; import org.junit.Test; import com.acme.user.entity.State; import com.acme.user.entity.StateDao; public class StateBoTest { private static final Integer ID_1 = new Integer( 1 ); private static final Integer ID_2 = new Integer( 2 ); private static final State ALABAMA = new State( ID_1, "us", "al", "Alabama" ); private static final State ALASKA = new State( ID_2, "us", "ak", "Fred" ); private static final String CODE = "code"; private static final String AL_CODE = "al"; private StateBo bo; // the business logic private StateDao dao; // interface atop database @Before public void setup() { this.bo = new StateBo(); // class under test this.dao = mock( StateDao.class ); // dependent class (mocked) this.bo.setDao( this.dao ); // every test needs this wiring } private final boolean checkStateResult( State control, State result ) { assertEquals( control.getId(), result.getId() ); assertEquals( control.getCountry(), result.getCountry() ); assertEquals( control.getCode(), result.getCode() ); assertEquals( control.getName(), result.getName() ); return true; } @Test public void testCreate() { // set up mocked result on create... when( this.dao.create( ALABAMA ) ).thenReturn( ALABAMA ); // call the method we wish to test... State result = this.bo.create( ALABAMA ); // verify the method was called... verify( this.dao ).create( result ); // verify correct results returned... assertNotNull( result ); assertTrue( checkStateResult( ALABAMA, result ) ); } @Test public void testFindAll() { // squirrel-cage the database... List< State > all = new LinkedList< State >(); all.add( ALABAMA ); all.add( ALASKA ); // return mocked result set on read... when( this.dao.read() ).thenReturn( all ); // call the method we want to test... List< State > result = this.bo.findAll(); // verify the method was called... verify( this.dao ).read(); // verify correct number of results returned... assertEquals( 2, result.size() ); // verify correct results returned... assertTrue( checkStateResult( ALABAMA, result.get( 0 ) ) ); assertTrue( checkStateResult( ALASKA, result.get( 1 ) ) ); } @Test public void testFindById() { // return mocked result set on readById... when( this.dao.readById( 1 ) ).thenReturn( ALABAMA ); // call the method we want to test... State result = this.bo.findById( 1 ); // verify the method was called... verify( this.dao ).readById( 1 ); // verify correct result returned... assertNotNull( result ); assertTrue( checkStateResult( ALABAMA, result ) ); } @Test public void testFindByPropertyAndValue() { // squirrel-cage the database... List< State > all = new LinkedList< State >(); all.add( ALABAMA ); all.add( ALASKA ); // return mocked result set on readByPropertyAndValue... when( this.dao.readByPropertyAndValue( CODE, AL_CODE ) ).thenReturn( all ); // call the method we want to test... List< State > result = this.bo.findByPropertyAndValue( CODE, AL_CODE ); // verify the method was called... verify( this.dao ).readByPropertyAndValue( CODE, AL_CODE ); // verify correct number of results returned... assertEquals( 2, result.size() ); // verify correct results returned... assertTrue( checkStateResult( ALABAMA, result.get( 0 ) ) ); assertTrue( checkStateResult( ALASKA, result.get( 1 ) ) ); } @Test public void testUpdate() { // stub mocked result on update... doNothing().doThrow( new IllegalStateException() ) .when( this.dao ).update( any( State.class ) ); // call the method we wish to test... this.bo.update( ALABAMA ); // verify the method was called... verify( this.dao ).update( ALABAMA ); // because there's no state to examine, we're done } @Test public void testDelete() { // stub mocked result on update... doNothing().doThrow( new IllegalStateException() ) .when( this.dao ).delete( ALABAMA ); /* Added complication for this method's test: * Set up mocked result for dao.readById() since bo.delete(), which * is under test, calls bo.findById() which itself calls dao.readById(), * but we haven't set up a mock result for that yet */ when( this.dao.readById( ID_1 ) ).thenReturn( ALABAMA ); // call the method we want to test... this.bo.delete( ID_1 ); // verify the method was called... verify( this.dao ).delete( ALABAMA ); // because there's no state to examine, we're done } }
Support from the Mockito Googlegroup refines what's above. If a method is nothing more than a pass-through (which it is here owing to the rather zero amount of business logic in the StateBo example) you don't really care about the properties of a State. If the code begins to do other things (i.e.: more business logic) then you will document it in the test with identifiable scenarios.
Also, the test name should reflect the scenario it's testing.
Comparing the two (original above and Brice Dutheil's rewrite below) is instructive.
... { ... @Test public void on_create_returns_same_instance() { when( this.dao.create( ALABAMA ) ).thenReturn( ALABAMA ); State result = this.bo.create( ALABAMA ); // no need for verify, otherwise the tests won't pass; the verification // in this context is implicit // there is no need to check the properties since StateBo is a pass-through assertSame( ALABAMA, result ); // however if StateBo begins to do something, you might want to replace // that by an "assertStateProperties", but then you will give another // name to the test like: // "on_create_ensure_properties_do_have_<your expected values or meaning>" } @Test public void should_invoke_DAO_on_delete_by_id() { // delete scenario // Mockito mocks do nothing by default, no need to mock that // ok this is needed when( this.dao.readById( ID_1 ) ).thenReturn( ALABAMA ); this.bo.delete( ID_1 ); // ok, you do need to verify that the interaction happened verify( this.dao ).delete( ALABAMA ); } @Test public void should_not_propate_on_second_delete_of_same_id() { // another delete scenario on successive delete of same id needed here // as part of the scenario doNothing().doThrow( new IllegalStateException() ) .when( this.dao ).delete( ALABAMA ); // this is still needed when( this.dao.readById( ID_1 ) ).thenReturn( ALABAMA ); // here we're testing something else, i.e. that StateBo doesn't // propagate the exception on the second delete call: there is one more // call to delete than there are calls to doNothing() above which will // force the IllegalStateException() to occur* this.bo.delete( ID_1 ); this.bo.delete( ID_1 ); // verify of the interaction not needed, it was already in the previous test } }
* this needs work to polish the concept.
Based on what I've learned writing short Mockito tests, many of which didn't work the first time, I've begun to create a sort of method for creating tests. I expect this to receive considerable modification over the near future.
How to use Mockito to test an abstract class when the class depends upon a strategy injected into its constructor?
// abstract class to be tested with mock instance... abstract BaseClass { // strategy gets mocked too protected BaseClass( Strategy strategy) { ... } }
One answer is to create a local class that inherits from the abstract one and test it.
Strategy strategyMock = mock( Strategy.class); when(....).thenReturn(...); BaseClass baseMock = mock( BaseClass.class, CALLS_REAL_METHODS ); baseMock.getClass().getSuperclass().getDeclaredField( "_privateStrategy" ).set( baseMock, strategyMock ); // do unit tests with baseMock...