Mock Testing DAOs in JPA-style Hibernate

25 January, 2014

Looking for how to do a decent mock test on a DAO layer using JPA-style hibernate, I found nothing that perfectly fit what we were doing, so I thought I'd put down an example and help add to the world of knowledge.

The point here is that all of the code is executed right up to actually trying to make a database call.  The query itself is mocked up, and so there is no external dependency.  This is useful if--for some reason or another--the team that controls your automated builds puts up a firewall between your build server and your database. Also useful if you can't rely on the data in your database to be stable.  Not so useful if you are getting failures calling out to the database, but you can always write non-mock tests for that purpose.  That's easier anyway.

I don't feel especially like explaining all this since I'm lazy.  That, and the links at the bottom include some decent explanations.  So have a look over there, and then hopefully the example will speak for itself.

Note that all of this code is simplified from real, working examples, but I haven't actually compiled it--hopefully it's good.  Anyway, onto some classes.

SillyTable.java

package com.intents.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;@SuppressWarnings("serial")

@Entity
@NamedQueries({
    @NamedQuery(name = "SillyTable.findByPrimaryKey", query = "select st from SillyTable st where st.primaryKey = :primaryKey")
})
@Table(name = "SILLY_TABLE")
public class SillyTable {
    @Id
    @GeneratedValue(generator = "primaryKeySeq")
    @SequenceGenerator(name = "primaryKeySeq", sequenceName = "PRIMARY_KEY_SEQ")
    @Column(name = "PRIMARY_KEY", nullable = false)
    private Long primaryKey;
}

SillyTableDao.java

package com.intents.dao;

import com.intents.model.SillyTable;

public interface SillyTableDao {
    String FIND_BY_PRIMARY_KEY_QUERY = "SillyTable.findByPrimaryKey";
    String PRIMARY_KEY_PARAMETER = "primaryKey";

    SillyTable findByPrimaryKey(Long primaryKey) throws Exception;
}

SillyTableDaoImpl.java

package com.intents.dao.impl;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.springframework.stereotype.Repository;
import com.intents.dao.SillyTableDao;
import com.intents.model.SillyTable;

@Repository
public class SillyTableDaoImpl implements SillyTableDao {
   
@PersistenceContext(unitName = "framework")
    private EntityManager entityManager;
    
    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public EntityManager getEntityManager() {
        return entityManager;
    }

    @Override
    public SillyTable findByPrimaryKey(final Long primaryKey) throws Exception {
        SillyTable result = null;
        Query query;
        try {
            query = getEntityManager().createNamedQuery(FIND_BY_PRIMARY_KEY_QUERY);
            query.setParameter(PRIMARY_KEY_PARAMETER, primaryKey);
            result = (SillyTable) query.getSingleResult();
        } catch (Exception e) {
            throw new Exception("Failed lookup! ZMOG!", e);
        }
        return result;
    }
}

SillyTableDaoImplMockTest.java

package com.intents.dao.impl;

import static org.junit.Assert.assertSame;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.when;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
import com.intents.dao.impl.SillyTableDaoImpl;
import com.intents.model.SillyTable;

@RunWith(MockitoJUnitRunner.class)
@Transactional
public class SillyTableDaoImplMockTest  {
    private static final String FIND_BY_PRIMARY_KEY_QUERY = "SillyTable.findByPrimaryKey";
    private static final Long PRIMARY_KEY = 9001L;

    @Mock
    EntityManager entityManager;

    @Mock
    Query query;

    SillyTableDaoImpl sillyTableDao = new SillyTableDaoImpl();

    @Before
    public void setup() {
        when(entityManager.find((Class<?>) any(), any())).thenReturn(null);
        sillyTableDao.setEntityManager(entityManager);
    }

    @Test
    @Rollback(true)
    public void testFindByPrimaryKeyReturnsResult() {
        SillyTable expectedSillyTable = new SillyTable();
        when(entityManager.createNamedQuery(FIND_BY_PRIMARY_KEY_QUERY)).thenReturn(query);
        when(query.getSingleResult()).thenReturn(expectedSillyTable);
        SillyTable returnedSillyTable = sillyTableDao.findByPrimaryKey(PRIMARY_KEY);
        assertSame(expectedSillyTable, returnedSillyTable);
    }

    @Test(expected = Exception.class)
    @Rollback(true)
    public void testFindByPrimaryKeyNoResultsThrowsException() throws Exception {
        when(entityManager.createNamedQuery(FIND_BY_PRIMARY_KEY_QUERY)).thenReturn(query);
        when(query.getSingleResult()).thenThrow(new NoResultException());
        sillyTableDao.findByPrimaryKey(PRIMARY_KEY);
    }
}

References Used

http://bitbybitblog.com/unit-testing-data-access-components/

http://www.luckyryan.com/2013/06/28/unit-testing-with-mockito/

http://api.nhindirect.org/java/site/direct-msg-monitor/1.1.3/xref-test/org/nhindirect/monitor/dao/impl/AggregationDAOImpl_confirmAggregationTest.html

 

 

 

New comment