Mockito prosty test z instrukcją warunkową

0

Witam,
Stawiam pierwsze kroki z Mockito. Napisałem test do trywialnej metody z instrukcją warunkową i nie otrzymuje spodziewanego wyniku.

Poniżej kod EJB, które chce przetestowć:

@Stateless
public class TaskFacade extends AbstractFacade<Task> implements TaskFacadeLocal {
    @PersistenceContext(unitName = "taskList-PU")
    private EntityManager em;

    @Override
    protected EntityManager getEntityManager() {
        return em;
    }

    public TaskFacade() {
        super(Task.class);
    }

    @Override
    public boolean taskExists(long id) {
        Task idTask = find(id);
        if (idTask == null) {
            return false;
        } else {
            return true;
        }
    }

}

Metoda abstrakcyjna będąca częścią generycznego DAO:

public abstract class AbstractFacade<T> {
    private Class<T> entityClass;

    public AbstractFacade(Class<T> entityClass) {
        this.entityClass = entityClass;
    }

    protected abstract EntityManager getEntityManager();

    public T find(Object id) {
        return getEntityManager().find(entityClass, id);
    }

}

Kod encji:

@Entity
@Table(name = "task")
@XmlRootElement
@NamedQueries({
    @NamedQuery(name = "Task.findAll", query = "SELECT t FROM Task t"),
    @NamedQuery(name = "Task.findByTaskId", query = "SELECT t FROM Task t WHERE t.taskId = :taskId"),
    @NamedQuery(name = "Task.findByName", query = "SELECT t FROM Task t WHERE t.name = :name"),
    @NamedQuery(name = "Task.findByDescription", query = "SELECT t FROM Task t WHERE t.description = :description"),
    @NamedQuery(name = "Task.findByStatus", query = "SELECT t FROM Task t WHERE t.status = :status"),
    @NamedQuery(name = "Task.findByBegda", query = "SELECT t FROM Task t WHERE t.begda = :begda"),
    @NamedQuery(name = "Task.findByEndda", query = "SELECT t FROM Task t WHERE t.endda = :endda")})
public class Task implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(generator = "task_seq", strategy = GenerationType.SEQUENCE)
    @SequenceGenerator(name = "task_seq", sequenceName = "task_task_id_seq",
            allocationSize = 1)
    @Basic(optional = false)
    @Column(name = "task_id")
    private Long taskId;
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 40)
    @Column(name = "name")
    private String name;
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 255)
    @Column(name = "description")
    private String description;
    @Basic(optional = false)
    @NotNull
    @Column(name = "status")
    private int status;
    @Basic(optional = false)
    @NotNull
    @Column(name = "begda")
    @Temporal(TemporalType.TIMESTAMP)
    private Date begda;
    @Basic(optional = false)
    @NotNull
    @Column(name = "endda")
    @Temporal(TemporalType.TIMESTAMP)
    private Date endda;

    public Task() {
        this.status = 1;
        this.begda = new Date();
        this.endda = new Date();
    }

    public Task(String name, String description) {
        this.name = name;
        this.description = description;
        this.status = 1;
        this.begda = new Date();
        this.endda = new Date();
    }

    public Long getTaskId() {
        return taskId;
    }

    public void setTaskId(Long taskId) {
        this.taskId = taskId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public Date getBegda() {
        return begda;
    }

    public void setBegda(Date begda) {
        this.begda = begda;
    }

    public Date getEndda() {
        return endda;
    }

    public void setEndda(Date endda) {
        this.endda = endda;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (taskId != null ? taskId.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Task)) {
            return false;
        }
        Task other = (Task) object;
        if ((this.taskId == null && other.taskId != null) || (this.taskId != null && !this.taskId.equals(other.taskId))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "pl.seaduc.tasklist.Task[ taskId=" + taskId + " ]";
    }

}

Poniżej prezentuje kod mojego testu.

public class TaskFacadeTest {

    private TaskFacade taskFacade = mock(TaskFacade.class);
    private static final long RET_TASK_ID = 1L;

    @Test
    public void testTaskExists() throws Exception {
        Task searchedTask = mock(Task.class);
        when(searchedTask.getTaskId()).thenReturn(RET_TASK_ID);
        when(taskFacade.find(1L)).thenReturn(searchedTask);
        assertTrue(RET_TASK_ID == searchedTask.getTaskId());
        assertTrue(taskFacade.taskExists(searchedTask.getTaskId()));
        verify(taskFacade).taskExists(searchedTask.getTaskId());
    }

}

Otrzymuje wynik niezgodny z oczekiwanien. Mockito zwraca domyślną wartość zamiast taką o jaką prosiłem, jakby ignorowało mój mock, gdy wywołam metodę find.

Wywala się drugi assert. Na czym polega mój błąd? Wygląda na to, że metoda find zamiast mocka zwraca null. Dlatego otrzymuje false zamiast true. Nie rozumiem jednak dlaczego.

0

twoja zmienna to mały long a mockujesz na duży

0

Dzięki za zainteresowanie. Nie rozwiązałem problemu, ale zmodyfikowałem test:

public class TaskFacadeTest {

    private TaskFacade taskFacade = mock(TaskFacade.class);
    private static final Long RET_TASK_ID = 1L;

    @Test
    public void testTaskExists() throws Exception {
        Task searchedTask = mock(Task.class);
        when(searchedTask.getTaskId()).thenReturn(RET_TASK_ID);
        when(taskFacade.find(RET_TASK_ID)).thenReturn(searchedTask);
        assertTrue(searchedTask.getTaskId() instanceof Long);
        assertTrue(RET_TASK_ID.compareTo(searchedTask.getTaskId()) == 0);
        assertTrue(taskFacade.taskExists(searchedTask.getTaskId()));
        verify(taskFacade).taskExists(searchedTask.getTaskId());
    }

}

Wciąz porażka na:

assertTrue(taskFacade.taskExists(searchedTask.getTaskId()));
0

Ale jak zrobiłeś mocka z taskFacade to nie nauczyłeś go co ma zrobić jak wywołasz na nim taskExists
albo zrób spaya albo callRealMethod..

0

W sumie to bez sensu sa te testy 2 posty wyzej. Sam mockujesz searchedTask zeby zwracal to i owo, a pozniej sprawdzasz co zwraca, jaki ma typ itp. Co ty chcesz tak testowac? To testuje czy mockito poprawnie dziala, ale raczej nie twoj kod.

0

A co do bledu to ten taskFacade to tez mock, ktory wie co zrobic jak sie na nim wywola find(), a oczekujesz ze magicznie bedzie wiedzil co robic jak sie wywola taskExists. Wez pod uwage ze ten mock jest glupi, i w zadnym razie nie umie wykonac metody ktora ty zaimplementowales.
Robienie spy jest niezalecane, to zle rozwiazanie.

Ogolnie ten test jest do d**y. Ty masz testowac swoj kod a nie mockito. Musisz utworzyc instancje TaskFacade, mockowac jej zaleznosci tak zeby robily to czego oczekujesz, wywolac metody na tym obiekcie facasady i sprawdzac poprawnosc. W tej chwili nie jest testowana ani jedna linijka kodu ktory chcesz testowac.

0

mućka, dzięki za wskazówki. A więc utworzyłem sobie prawdziwe obiekty TaskFacade oraz Task.

Czy dobrze zrozumiałem:

  • muszę zmockować entityManager, tak aby mieć protezę pod metodę find

Jak to ma się do zdania na stronie Mockito:
"Only mock types you own": wyczuwam sprzeczność.

public class TaskFacadeTest {

    private TaskFacade taskFacade = new TaskFacade();
    private static final Long RET_TASK_ID = 1L;

    @Test
    public void testTaskExists() throws Exception {
        Task searchedTask = new Task();
        searchedTask.setTaskId(RET_TASK_ID);
        
        // TO DO:
        // zmockowac typy, od ktorych zalezy TaskFacade
        
        assertTrue(taskFacade.taskExists(searchedTask.getTaskId()));
        verify(taskFacade).taskExists(searchedTask.getTaskId());
    }

}

0

@student911 musisz zadać sobie proste pytanie: co ty w ogóle chcesz przetestować? Bo teraz to robisz jakieś cuda na kiju metodą prób i błędów. CO chcesz przetestować? Testując metodę X mockujesz wszystkie jej zależności i testujesz tylko ją. W twoim przypadku użyłbym w ogóle PowerMockito i zrobił sobie partial mock z klasy którą testujesz, tak żeby zamockować wywołania innych metod tej klasy/klas bazowych i testowałbym metody w izolacji od reszty kodu.
Więc będziesz miał coś w stylu

TaskFacade objectUnderTest = createNicePartialMockForAllMethodsExcept(TaskFacade.class, "taskExists");
when(objectUnderTest.find()).thenReturn(null);

i voila testujesz pierwsze możliwe przejście przez ten kod. Następnie w drugim teście robisz analogniczie tylko zwracasz nie-nulla i voila.

0

W tej zasadzie chodzi o to ze tak naprawde mozesz nie wiedziec jak dziala dana klasa i zmockujesz ja zle lub jej dzialanie sie zmieni i mock nagle przestanie dzialac poprawnie. Ogolnie ma ona sens, ale nie przesadzalbym - raczej em.find() nie zmieni ani swojego dzialania, ani swojej nazwy, itp. wiec zrobienie mocka jest tak proste ze szkoda nawet nad tym rozmyslac.

Jesli nie chcesz, mozesz utworzyc prawdziwego em, z jakas baza danych w pamieci jak h2, wsadzic cos do bazy tak zeby em to cos znalazl. Sa tu pewne problemy: a) wydaje mi sie ze takie cos jest gorsze, poniewaz tutaj juz naprawde musisz sie zaglebiac - jak dodac do bazy cos zeby em to znalazl? b) musisz uzyc prawdziwego providera c) miec jakas baze danych
To juz nie jest unit test, i nie wymaga wcale mockito, bo nie masz zadnego mocka.

Ogolnie to powinienes zrobic cienka abstrakcje powyzej em (DAO pattern czy podobne), i twoje fasady powinny korzystac z tego. Wtedy, mockowanie tego DAO w fasadzie juz jest dozwolone (bo to twoja klasa), tak? A jak przetestujesz to DAO? Albo nie testujesz, albo mockujesz em, albo zatrudniasz prawdziwego providera i baze danych. Innych opcji nie znam, decyzja nalezy do Ciebie.

Co nie zmienia fakty, ze poprzednie testy sa do niczego, mozna je wywalic na smietnik.

0

A po co tutaj PowerMockito i jego magia? Vanilla Mockito daje rade.

0

Osobiście wolę EasyMocka od Mockito więc nie jestem specjalnie zorientowany co tam jest a czego nie ma ;) Jak są częściowe mocki to ok.

0

Czesciowe mocki sa złe ;d

0

Niekoniecznie. Szczególnie kiedy masz w kodzie coś w stylu template method które chcesz przetestować ;) Albo właśnie takie metody które wywołują dużo innych metod a chcesz testować faktycznie jednostkowo (czyli tylko flow takiej metody)

0

Shalom i mućka: dziękuje za wszystko, czym się podzieliliście. Póki co użyłem vanillia Mockito. Na PowerMock i testy w izolacji przyjdzie czas.

Wydaje mi się, że robiąc test spying dokonuje właśnie testu w izolacji.

public class TaskFacadeTest {

    private static final Long RET_TASK_ID = 1L;

    @Test
    public void testTaskNotExists() throws Exception {
        TaskFacade objectUnderTest = mock(TaskFacade.class);
        when(objectUnderTest.find(RET_TASK_ID)).thenReturn(null);
        when(objectUnderTest.taskExists(RET_TASK_ID)).thenCallRealMethod();
        assertFalse(objectUnderTest.taskExists(RET_TASK_ID));
        verify(objectUnderTest).taskExists(RET_TASK_ID);
        assertFalse(objectUnderTest.taskExists(10L));
        verify(objectUnderTest).taskExists(10L);
    }

    @Test
    public void testTaskExists() throws Exception {
        TaskFacade objectUnderTest = mock(TaskFacade.class);
        Task a = new Task();
        a.setTaskId(RET_TASK_ID);
        when(objectUnderTest.find(RET_TASK_ID)).thenReturn(a);
        when(objectUnderTest.taskExists(RET_TASK_ID)).thenCallRealMethod();
        assertTrue(objectUnderTest.taskExists(RET_TASK_ID));
        verify(objectUnderTest).taskExists(RET_TASK_ID);
    }

}

A co sądzicie o tych testach? W zasadzie robią to co chcę. Potrafiąc napisać tak prosty test poradzę sobie z wieloma metodami powiązanymi z EJB, gdzie występuje flow.

1 użytkowników online, w tym zalogowanych: 0, gości: 1