Home > front end >  Is asserting mocked object and method necessary in Unit Test(JUnit 5)?
Is asserting mocked object and method necessary in Unit Test(JUnit 5)?

Time:03-18

I'm still a basic learner for Unit Test.

I'm trying to mock my class's method without directly calling it(since it is dependent on 3rd party library) and I wrote a test method like below.

(The 3rd party library which I mentioned is not MapStruct, it is ModelObject.class and it has very complicated parameters for the constructor and is only available to initialize in library level)

The ProjectMapper class's toDto method is a simple object mapping method(with MapStruct library).

@Test
@DisplayName("Should convert Project ModelObject to Project DTO successfully.")
void testProjectModelObjectToDtoSucess() {
    // Given 
    ModelObject mockModelObject = mock(ModelObject.class); // <-- Mocking "ModelObject.class" 

    ProjectDto expected = new ProjectDto("PPJT-00000001", "Test Project 01"); // <-- Initializing expected return object.

    when(mockProjectMapper.toDto(mockModelObject)).thenReturn(expected); // <-- Mocking cut(class under test)'s method. 

    // When 
    ProjectDto actual = mockProjectMapper.toDto(mockModelObject); // <-- Calling mocked method. 

    // Then 
    assertEquals(actual.getId(), expected.getId(), "The converted DTO's ID should equal to expected DTO's ID.");
    assertEquals(actual.getName(), expected.getName(), "The converted DTO's name should equal to expected DTO's name.");
}

What I want to know is if I already assume mockProjectMapper.toDto() will return exactly equaled expected object, why would I need to assert with the actual(returned) object?

I learned that Unit Test should test any codes which can be breakable.

I doubt what's the benefit of this way of the test and if it's inappropriate, what's the correct way to test this method with mocking?

I referenced this test method from this example code.

@refael-sheinker Here's the source code of the ProjectMapper class which is MapStruct's interface class.

@Mapper
public interface ProjectMapper {
    ProjectMapper INSTANCE = Mappers.getMapper(ProjectMapper.class);

    @Mapping(target = "id", source = "projectId")
    @Mapping(target = "name", source = "projectName")
    ProjectDto toDto(ModelObject modelObject);
}

CodePudding user response:

You mocked your object under test and stubbed method under test - and you correctly concuded that such a test brings no benefit.

You are checking that you can stub a method, not your production code.

The main problem is your assumption that you need to mock every 3rd party class. This is wrong way of thinking - you should try to mock collaborators of your class (especially ones that cause side-effects unwanted in the test environment), but not the classes which are critical part of implementation of your class. The line might be sometimes blurry, but in this concrete example I strongly recommend NOT mocking the mapper:

  • your 3rd party class is driven by annotations
  • the annotations must correspond to your DTOs fields
  • the field names in annotations are Strings, their presence is DTOs is not enforced by compiler
  • all mapping is done in the memory

Creating a test that exercises production code gives you confidence that:

  • field names are correct
  • corresponding mapped fields have correct types (for example, you are not mapping int to boolean)

The linked test of the service is somewhat different: it tests a Spring service which calls the repository. The repository presumably represents some DB, which interacts with the outside world - it is a good candidate for a mock. To my eyes the service method only forwards the call to the repository - which is a trivial behaviour - so the test is trivial, but this setup will scale for more complicated service methods.

Update

Mocking source class for MapStruct.

If ModelObject is complicated to create, consider using mocks for some of its constructor parameters.

var pN = mock(ParamN.class);
// possibly stub some interactions with pN
var modelObject = new ModelObject(10, 20, pN);

Alternatively, you could even mock entire ModelObect and stub its getters:

var mockModelObject = mock(ModelObject.class); 
when(mockModelObject.getId()).thenReturn(10); 
  • Related