Mockning - @InjectMocks med Mockito

När ett test skrivs för en klass som har beroenden till en datakälla, en extern service eller bara en annan klass är mockning ofta väldigt användbart. Ibland kan detta leda till att produktionskod anpassas för att det ska gå att skriva dessa tester. Nedan är exempel på ett test som testar en service som använder en entity manager.

package se.cygni.blog;
 
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
 
import javax.persistence.EntityManager;
 
import org.junit.Before;
import org.junit.Test;
 
public class BlogServiceTest {
 
    private BlogService service;
    private EntityManager entityManager;
 
    @Before
    public void setup() {
        entityManager = mock(EntityManager.class);
        service = new BlogService(entityManager);
    }
 
    @Test
    public void addEntry() {
        Entry entry = new Entry("title", "text");
 
        service.addEntry(entry);
 
        verify(entityManager).persist(entry);
    }
}

Testet i exemplet är relativt enkelt och verifierar endast att persist anropas i addEntry och ger oss följande kod för ett Spring-baserat projekt:

package se.cygni.blog;
 
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
 
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
@Service
public class BlogService {
 
    @PersistenceContext
    private EntityManager entityManager;
 
    public BlogService() {
    }
 
    protected BlogService(EntityManager entityManager) {
        this.entityManager = entityManager;
    }
 
    @Transactional
    public void addEntry(Entry entry) {
        entityManager.persist(entry);
    }
}

För att kunna använda en mockad entityManager har vi lagt till en konstruktor som tar en EntityManager som BlogService sedan kan använda. Använder vi dessutom ”field injection” är vi tvugna att lägga till en no-args-konstruktor. Alternativt hade vi kunnat lägga till en setter istället.

protected void setEntityManager(EntityManager entityManager) {
    this.entityManager = entityManager;
}

Det är väl antagligen inte så illa att behöva lägga till konstruktorer alternativt setters även om det blir värre när en klass har flera beroenden. Däremot känns det ju lite onödigt när både Spring och Java EE numera stödjer field injection. Det går ju att köra enhetstesterna med till exempel Spring och låta Spring injicera alla beroenden. Detta anses dock inte helt lämpligt för enhetstester utan lämpar sig bättre för integrationstester. Istället kan Mockito ta hand om injicering, genom att köra testerna med MockitoJUnitRunner och använda annotationerna @Mock och @InjectMocks. Mockito fungerar då i princip som en enkel IOC-container och några extra konstruktorer eller setters behövs inte.

package se.cygni.blog;
 
import static org.mockito.Mockito.verify;
 
import javax.persistence.EntityManager;
 
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
 
@RunWith(MockitoJUnitRunner.class)
public class BlogServiceTest {
 
    @Mock
    private EntityManager entityManager;
 
    @InjectMocks
    private BlogService service = new BlogService();
 
    @Test
    public void addEntry() {
        Entry entry = new Entry("title", "text");
 
        service.addEntry(entry);
 
        verify(entityManager).persist(entry);
    }
}