1

I got the following class with which I want to test my repository:

package de.gabriel.vertretungsplan.repositories;

import de.gabriel.vertretungsplan.models.Fach;
import de.gabriel.vertretungsplan.models.Klasse;
import de.gabriel.vertretungsplan.models.StundenplanEintrag;
import de.gabriel.vertretungsplan.models.Tag;
import de.gabriel.vertretungsplan.models.enums.Anwesenheit;
import de.gabriel.vertretungsplan.models.enums.Rolle;
import de.gabriel.vertretungsplan.models.enums.Tage;
import de.gabriel.vertretungsplan.models.users.Lehrer;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ActiveProfiles;

import java.util.List;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

@DataJpaTest
@ActiveProfiles("test")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class StundenplanEintragRepositoryTest {

    @Autowired
    private StundenplanEintragRepository stundenplanEintragRepository;
    @Autowired
    private KlasseRepository klasseRepository;
    @Autowired
    private FachRepository fachRepository;
    @Autowired
    private LehrerRepository lehrerRepository;
    @Autowired
    private TagRepository tagRepository;

    private Lehrer deutschLehrer;
    private Tag montag;
    private StundenplanEintrag stundenplanEintragReference;

    @BeforeAll
    void init() {
        Klasse klasse6a = new Klasse("6a");

        klasseRepository.saveAll(List.of(
                klasse6a
        ));

        Fach deutsch = new Fach("Deutsch", "DE");

        fachRepository.saveAll(List.of(
                deutsch
        ));

        deutschLehrer = new Lehrer("deutschLehrer", "deutschLehrer", Rolle.getPrefixedRolle(Rolle.LEHRER), Anwesenheit.ANWESEND);
        deutschLehrer.setFaecher(List.of(deutsch));

        lehrerRepository.saveAll(List.of(
                deutschLehrer
        ));

        montag = new Tag(Tage.MONTAG);

        tagRepository.saveAll(List.of(
                montag
        ));

        stundenplanEintragReference = new StundenplanEintrag(1, klasse6a, deutsch, deutschLehrer, montag);

        stundenplanEintragRepository.saveAll(List.of(
                stundenplanEintragReference
        ));
    }

    @AfterAll
    void tearDown() {
        stundenplanEintragRepository.deleteAll();
    }

    @Test
    @DisplayName("Check if correct Stundenplan Eintrag is returned given Lehrer [deutschLehrer] and Tag [montag]")
    void findByLehrerAndTag() {
        List<StundenplanEintrag> stundenplanEintrag = stundenplanEintragRepository.findByLehrerAndTag(deutschLehrer, montag);
        assertThat(stundenplanEintrag.get(0).getId()).isEqualTo(stundenplanEintragReference.getId());
    }

    @Test
    @DisplayName("Check if correct Stundenplan Eintrag is returned given Lehrer [deutschLehrer]")
    void findByLehrer() {
        List<StundenplanEintrag> stundenplanEintrag = stundenplanEintragRepository.findByLehrer(deutschLehrer);
        assertThat(stundenplanEintrag.get(0).getLehrer().getUsername()).isEqualTo(deutschLehrer.getUsername());
    }

}

As you can see currently I am comparing the result from the repository by a single field with the reference object (like the id or username), because if I compare them like this:

    @Test
    @DisplayName("Check if correct Stundenplan Eintrag is returned given Lehrer [deutschLehrer] and Tag [montag]")
    void findByLehrerAndTag() {
        List<StundenplanEintrag> stundenplanEintrag = stundenplanEintragRepository.findByLehrerAndTag(deutschLehrer, montag);
        assertThat(stundenplanEintrag.get(0)).isEqualTo(stundenplanEintragReference);
    }

    @Test
    @DisplayName("Check if correct Stundenplan Eintrag is returned given Lehrer [deutschLehrer]")
    void findByLehrer() {
        List<StundenplanEintrag> stundenplanEintrag = stundenplanEintragRepository.findByLehrer(deutschLehrer);
        assertThat(stundenplanEintrag.get(0).getLehrer()).isEqualTo(deutschLehrer);
    }

the tests fail because the adress of the actual objects differ from the "expected" objects adress even if they are "the same" objects.

Expected :de.gabriel.vertretungsplan.models.StundenplanEintrag@38d895e8
Actual   :de.gabriel.vertretungsplan.models.StundenplanEintrag@1b897ffb

I understand that it gets a new adress in memory (wrong as pointed out in the comments, it's the hash code). I want to know if it is a good practice to compare by something like the id or how else you would solve this or if there is a better practice to compare the object returned by a repository with the originally saved object? (And I still want to be able to save the entity only once in the @BeforeAll annotated method)

Gabriel
  • 233
  • 1
  • 3
  • 11
  • The number you are seeing is not the memory address, it's the hash code. You have two options to fix your assertion: Either write out all fields you want to compare in their own asserts, or add an `equals` method to your entity that compares the fields internally. – Jorn Jun 30 '23 at 13:12
  • @Jorn so comparing the fields is the best practice for comparing the 2 objects? – Gabriel Jun 30 '23 at 13:17
  • It's not about best practice, it's about what you need. Do you care that the field values are the same? Just the primary key? Or should it be the exact same object? That's a functional question that I can't answer for you. – Jorn Jun 30 '23 at 13:19
  • Sorry, the question maybe was a little bit unclear. I want to make sure that the object returned by the repository is exactly the object I saved. I guess the primary key would be the best practice to compare the 2 objects, as it is unique, but I wanted to make sure I didn't miss any important/better method in the assertj docs – Gabriel Jun 30 '23 at 13:24

2 Answers2

1

Generally I see nothing bad in comparison IDs in the test. It's pretty obvious that you operate with records and assert if it's the same record (the same record has the same ID).

As an option, of course, you may consider to make your solution more complex and override equals()(and hashCode()) methods of your class. It will make the solution more elegant, so you encapsulate in the class the definition of "being the same". You can define field(s) that needs to be equal in order to consider the objects as equal.

I do not think there is a "right" answer to your question. It depends... Check the discussions on the Internet and get the idea in which situations you want to override equals and in which situations you don't. Here are the couple of such a discussions:

  1. https://softwareengineering.stackexchange.com/questions/399265/do-we-always-need-to-override-equals-hashcode-when-crreating-a-new-class

  2. Why do I need to override the equals and hashCode methods in Java?

As I said, for the start, I'd probably compare IDs and not go the path of overriding, especially if you're not feeling confident with that.

Andrej Istomin
  • 2,527
  • 2
  • 15
  • 22
  • Thank you very much, I could remember the hashCode() method a little bit but I now learned the day about hashing, hashTables (which implementations often use these hashes) etc. and now got a hopefully unique hash for my Lehrer class that ignores the difference between an empty list and null value. If I want to compare the hashCodes with AssertJ I would still use the ```isEqualTo()``` method right? ```assertThat(anwesend.get(0).hashCode()).isEqualTo(chemielehrer.hashCode());``` – Gabriel Jul 01 '23 at 20:18
  • @Gabriel `hashCode` returns primitive `int`, you may freely use `==` to save some typing :) – Andrej Istomin Jul 02 '23 at 03:46
1

There is no a best practice in your case, you should define the comparison rules by yourself considering the business logic. The only relevant practice I would highlight is having test names so they reflect the actual given/when/then parts of the tests.

Maksym Kosenko
  • 515
  • 7
  • 12