UnitTestBot / UTBotJava

Automated unit test generation and precise code analysis for Java
Apache License 2.0
137 stars 45 forks source link

Do not mock Entities in Spring unit tests #2341

Open EgorkaKulikov opened 1 year ago

EgorkaKulikov commented 1 year ago

Description

For Spring projects we use custom mocking strategy that is similar to Mock other classes, but may contain type replacements. It seems that we should add one more customization: mocking classes marked @Entity is useless as they may be easily constructed with symbolic engine.

It is better to start with an example where we mock entities now and we will not do it with new approach.

EgorkaKulikov commented 1 year ago

@IlyaMuravjov could you provide an example of the behaviour we would like to improve here? I tried to construct it manually and realised that I do not catch a point.

IlyaMuravjov commented 1 year ago

@EgorkaKulikov when I generate unit tests for OwnerController.findOwner() method in spring-petclinic project with or without configuration I get the following test, that mocks Owner constructor (and also for some reason creates expectedMock variable that is never used in any asserts):

/**
 * @utbot.classUnderTest {@link OwnerController}
 * @utbot.methodUnderTest {@link OwnerController#findOwner(Integer)}
 * @utbot.executesCondition {@code (ownerId == null): True}
 * @utbot.returnsFrom {@code return ownerId == null ? new Owner() : this.owners.customFindById(ownerId);}
 */
@Test
@DisplayName("findOwner: ownerId == null : True -> return ownerId == null ? new Owner() : this.owners.customFindById(ownerId)")
public void testFindOwner_OwnerIdEqualsNull() throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
    MockedConstruction mockedConstruction = null;
    try {
        mockedConstruction = mockConstruction(Owner.class, (Owner ownerMock, Context context) -> {
        });

        Owner actual = ownerController.findOwner(null);

        Owner expectedMock = mock(Owner.class);
        setField(expectedMock, "org.springframework.samples.petclinic.owner.Owner", "address", null);
        setField(expectedMock, "org.springframework.samples.petclinic.owner.Owner", "city", null);
        setField(expectedMock, "org.springframework.samples.petclinic.owner.Owner", "telephone", null);
        setField(expectedMock, "org.springframework.samples.petclinic.owner.Owner", "pets", null);
        setField(expectedMock, "org.springframework.samples.petclinic.model.Person", "firstName", null);
        setField(expectedMock, "org.springframework.samples.petclinic.model.Person", "lastName", null);
        setField(expectedMock, "org.springframework.samples.petclinic.model.BaseEntity", "id", null);
        String actualAddress = ((String) getFieldValue(actual, "org.springframework.samples.petclinic.owner.Owner", "address"));
        assertNull(actualAddress);

        String actualCity = ((String) getFieldValue(actual, "org.springframework.samples.petclinic.owner.Owner", "city"));
        assertNull(actualCity);

        String actualTelephone = ((String) getFieldValue(actual, "org.springframework.samples.petclinic.owner.Owner", "telephone"));
        assertNull(actualTelephone);

        List actualPets = actual.pets;
        assertNull(actualPets);

        String actualFirstName = ((String) getFieldValue(actual, "org.springframework.samples.petclinic.model.Person", "firstName"));
        assertNull(actualFirstName);

        String actualLastName = ((String) getFieldValue(actual, "org.springframework.samples.petclinic.model.Person", "lastName"));
        assertNull(actualLastName);

        Integer actualId = ((Integer) getFieldValue(actual, "org.springframework.samples.petclinic.model.BaseEntity", "id"));
        assertNull(actualId);

    } finally {
        mockedConstruction.close();
    }
}

I also get the same test when I disable Mock static methods, but this time I also get the following warning.

/* WARNING!!! Automatically used "Mockito static mocking" framework for mocking statics
because execution encountered flaky methods
To change this behaviour edit [Settings -> UtBot -> Force static mocking] */

When I disable both Mock static methods and Force static mocking (they both were enabled by default) I finally able to get a test, that doesn't mock Owner constructor, although it still renders a warning and creates unused expectedMock.

/**
 * @utbot.classUnderTest {@link OwnerController}
 * @utbot.methodUnderTest {@link OwnerController#findOwner(Integer)}
 * @utbot.executesCondition {@code (ownerId == null): True}
 * @utbot.returnsFrom {@code return ownerId == null ? new Owner() : this.owners.customFindById(ownerId);}
 */
@Test
@DisplayName("findOwner: ownerId == null : True -> return ownerId == null ? new Owner() : this.owners.customFindById(ownerId)")
public void testFindOwner_OwnerIdEqualsNull() throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
    /* Warning!!! This test can be flaky because execution encountered flaky methods,
    but no "static mocking" was selected */

    Owner actual = ownerController.findOwner(null);

    Owner expectedMock = mock(Owner.class);
    setField(expectedMock, "org.springframework.samples.petclinic.owner.Owner", "address", null);
    setField(expectedMock, "org.springframework.samples.petclinic.owner.Owner", "city", null);
    setField(expectedMock, "org.springframework.samples.petclinic.owner.Owner", "telephone", null);
    setField(expectedMock, "org.springframework.samples.petclinic.owner.Owner", "pets", null);
    setField(expectedMock, "org.springframework.samples.petclinic.model.Person", "firstName", null);
    setField(expectedMock, "org.springframework.samples.petclinic.model.Person", "lastName", null);
    setField(expectedMock, "org.springframework.samples.petclinic.model.BaseEntity", "id", null);
    String actualAddress = ((String) getFieldValue(actual, "org.springframework.samples.petclinic.owner.Owner", "address"));
    assertNull(actualAddress);

    String actualCity = ((String) getFieldValue(actual, "org.springframework.samples.petclinic.owner.Owner", "city"));
    assertNull(actualCity);

    String actualTelephone = ((String) getFieldValue(actual, "org.springframework.samples.petclinic.owner.Owner", "telephone"));
    assertNull(actualTelephone);

    List actualPets = actual.pets;
    assertNull(actualPets);

    String actualFirstName = ((String) getFieldValue(actual, "org.springframework.samples.petclinic.model.Person", "firstName"));
    assertNull(actualFirstName);

    String actualLastName = ((String) getFieldValue(actual, "org.springframework.samples.petclinic.model.Person", "lastName"));
    assertNull(actualLastName);

    Integer actualId = ((Integer) getFieldValue(actual, "org.springframework.samples.petclinic.model.BaseEntity", "id"));
    assertNull(actualId);

}