Why does entityManager.clear() fails my tests due to different date values even though I am not changing any of them?

40 views Asked by At

So I am running a @DataJpaTest and in my setUp function, I do entityManager.clear() which ends up detaching the books objects from the persistence context. But the following test ends up failing with the error.

Error when comparing the whole Book entity ->

BookRepositoryTest > doSomething() FAILED
    org.opentest4j.AssertionFailedError: expected: <Book(id=6, title=Pride and Prejudice, price=16.99, quantity=12, author=Jane Austen, ISBN=890-1234567890, genre=Romance, publishDate=1813-01-28)> but was: <Book(id=6, title=Pride and Prejudice, price=16.99, quantity=12, author=Jane Austen, ISBN=890-1234567890, genre=Romance, publishDate=1813-01-27)>
        at app//org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
        at app//org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
        at app//org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
        at app//org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
        at app//org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:177)
        at app//org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1145)
        at app//library.backend.api.BookRepositoryTest.doSomething(BookRepositoryTest.java:55)

Error when only comparing the dates ->

BookRepositoryTest > doSomething() FAILED
    org.opentest4j.AssertionFailedError: The dates do not match ==> expected: <1813-01-28> but was: <1813-01-27>
        at app//org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
        at app//org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
        at app//org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
        at app//org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
        at app//org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1156)
        at app//library.backend.api.BookRepositoryTest.doSomething(BookRepositoryTest.java:55)

But the moment I comment out the entityManager.clear() line, the tests pass. Anyone has any idea why this happens? I am not making any changes to any of the books so why are the dates being changed when reading from the db?

@ExtendWith(SpringExtension.class)
@DataJpaTest
@ActiveProfiles(profiles = {"test"})
public class BookRepositoryTest {
    @Autowired
    private BookRepository bookRepository;

    @Autowired
    private TestEntityManager entityManager;

    private ObjectMapper mapper;
    
    @Value("classpath:books.json")
    Resource booksJson;

    List<Book> books;

    @BeforeEach
    void setUp() throws Exception{
        mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        books = mapper.readValue(booksJson.getInputStream(),
                            new TypeReference<List<Book>>(){});
        books.forEach(entityManager::persistAndFlush);
        entityManager.clear();
    }

    @Test
    void doSomething(){
        List<Book> firstSixBooks = bookRepository.findAll(PageRequest.of(0,6)).toList();
        assertEquals(6, firstSixBooks.size());
        for(int j=0; j<6; j++){
            assertEquals(books.get(j).getPublishDate(), firstSixBooks.get(j).getPublishDate());
        }
    }

}

Any help or advices are appreciated!

I am expecting for the tests to pass even with entityManager.clear() and it is ends up failing, I want to know why out of curiosity.

2

There are 2 answers

3
John Bollinger On

I am expecting for the tests to pass even with entityManager.clear() and it is ends up failing, I want to know why out of curiosity.

When you persist an entity object using an EntityManager, that object itself becomes persistent. Among other things, it means that that object is cached by the EntityManager. That's perhaps the main thing that distingishes a persistent entity from a detached one.

When an EntityManager is asked for an entity that it already has cached, it provides the cached instance itself, not a copy. This is what happens in your test when you do not detach the entities. In this case, you are testing whether objects are equals() themselves, which is true of any Object that does not have a perverse, non-conforming equals(Object) method. It is in no way surprising, then, that the equals() tests pass.

But if you do detach the entities, by clear()ing the EntityManager, for example, then it must instantiate new ones when you request them. In this case, you compare distinct objects with equal property values. These will compare equal if and only if you have given the entity class an equals(Object) method that produces that result. The one inherited from Object will not produce that result.

Especially for entities, it's debatable whether you should equip classes with such an equals() method. If you decide to do, however, then you should make sure to implement a compatible hashCode() method as well.

IMO, your test fixture should clear the EntityManager. If you decide to give the Book class an equals() method corresponding to the conditions you want to test then that should be sufficient to make your existing test pass. Otherwise, you should modify the test so that it evaluates whatever sense of equality you want to check. For example, that might mean comparing one or more individual fields.

0
Eduard A On

I have updated the doSomething() test method to compare the dates as strings by calling toString() on the LocalDate objects. This modification allows you to compare the date values themselves rather than their object references, which can help ensure consistent results in your tests

@Test
void doSomething(){
    List<Book> firstSixBooks = bookRepository.findAll(PageRequest.of(0,6)).toList();
    assertEquals(6, firstSixBooks.size());
    for(int j=0; j<6; j++){
        // Compare dates as strings to avoid object reference issues
        assertEquals(books.get(j).getPublishDate().toString(), firstSixBooks.get(j).getPublishDate().toString());
    }
}