ModelMapper 使用 JUnit Mockito 抛出 NPE

ModelMapper throws NPE using JUnit Mockito

我正在使用 ModelMapper 进行 NPE

目录服务测试

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class CatalogServiceTest {

    @Rule
    public ExpectedException thrown = ExpectedException.none();
    @InjectMocks private CatalogService service;
    @Mock ModelMapper modelMapper;
    @Mock CatalogMapper catalogMapper;
    @Mock CatalogRepository catalogRepository;


    @Before
    public void setUp() throws Exception {
//        MockitoAnnotations.initMocks(this);
        CatalogEntity catalogEntity = new CatalogEntity();
        catalogEntity.setId("id");
        catalogEntity.setCode("code");
        catalogEntity.setType("type");
        catalogEntity.setValue("value");

//        Optional<CatalogEntity> optionalCatalog = Optional.of(catalogEntity);
        when(catalogRepository.findByCode(any(String.class))).thenReturn(catalogEntity);
    }

    @Test
    public void whenFindByCode() {
        //Act
        CatalogDto myCatalogDto = service.findByCode("code");
        //Assert
        assertTrue(myCatalogDto.getCode().equals("code"));
    }
}

目录服务

@Service
public class CatalogService {

    private static final Logger LOGGER = LoggerFactory.getLogger(CatalogService.class);

    @Autowired
    CatalogRepository catalogRepository;

    @Autowired
    CatalogMapper catalogMapper;

    /**
     * 
     * @param type
     * @return catalog objects which type is type
     */
    public List<CatalogDto> findByType(String type) {
        LOGGER.info("Getting catalogs by type {}", type);
        List<CatalogEntity> catalogsEntityList = catalogRepository.findByType(type);
        List<CatalogDto> catalogDtoList = new ArrayList<>();
        catalogsEntityList.forEach(catalogEntity -> {
            catalogDtoList.add(catalogMapper.convertCatalogEntityToCatalogDto(catalogEntity));
        });
        return catalogDtoList;
    }
    
    /**
     * Find catalog by code.
     * @param code
     * @return catalog
     */
    public CatalogDto findByCode(String code) {
        LOGGER.info("Getting catalogs by code {}", code);
        return catalogMapper.convertCatalogEntityToCatalogDto(catalogRepository.findByCode(code));
    }
}

目录映射器

@Component
public class CatalogMapper {
    @Autowired
    private ModelMapper modelMapper;
    
    /**
     * Converts CatalogEntity object to CatalogDto object
     * @param catalogEntity
     * @return converted CatalogDto object
     */
    public CatalogDto convertCatalogEntityToCatalogDto(CatalogEntity catalogEntity) {
        return modelMapper.map(catalogEntity, CatalogDto.class);
    }
}

目录库

@Repository
public interface CatalogRepository extends MongoRepository<CatalogEntity, String> {

    List<CatalogEntity> findByType(String type);

    CatalogEntity findByCode(String code);
    
}

问题

catalogRepository.findByCode(code) 按预期 returning CatalogEntity 对象,问题出现在执行 catalogMapper.convertCatalogEntityToCatalogDto(catalogRepository.findByCode(code)); that return null.

之后

我正在使用 Intellij,这是刚好在执行 catalogMapper.convertCatalogEntityToCatalogDto 函数之前的断点。

catalogMapper 是一个没有存根方法的模拟。

有几种方法可以修复测试:

选项 1:仅测试与 CatalogMapper 的交互

在此选项中,您将对 catalogMapper.convertCatalogEntityToCatalogDto 的调用存根。这是一个精简单元测试,您只测试与协作服务的交互。

正如您所说,您想要测试映射器的实际实现,有两种选择:

方案二:使用SpringBootTest

在此选项中,您依赖 SpringBootTest 来设置整个应用程序上下文。

您需要进行以下更改:

  • 使用 @Autowired 而不是 @InjectMock 让你的对象受到测试
  • 对存储库使用 @MockBean 而不是 @MockSpringBootTest 忽略 @Mock.
  • 摆脱其他模拟
  • 因为它会创建整个应用程序上下文,所以我会排除此选项,除非完整的集成测试是您的目标(您在代码中以 @SpringBootTest 开始)
@SpringBootTest
public class CatalogServiceTest {

    @Rule
    public ExpectedException thrown = ExpectedException.none();
    @Autowired
    private CatalogService service;
    @MockBean
    CatalogRepository catalogRepository;

}

选项 3:自行构建您需要的服务

  • 去掉@SpringBootTest
  • 仅模拟您想要模拟的对象 - 存储库
  • 为其他服务创建真实对象
  • 您可能需要在您的服务中将字段注入更改为构造函数注入,无论如何这是个好主意
@Service
public class CatalogService {

    final CatalogRepository catalogRepository;

    final CatalogMapper catalogMapper;

    @Autowired
    public CatalogService(CatalogRepository catalogRepository, CatalogMapper catalogMapper) {
        this.catalogRepository = catalogRepository;
        this.catalogMapper = catalogMapper;
    }
}

这种方法只创建测试使用的对象,而不是整个应用程序上下文,因此可能会导致比选项 2 更精简的测试。

@RunWith(MockitoJUnitRunner.class)
public class CatalogServiceTest {

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    private CatalogService service;
    @Mock
    CatalogRepository catalogRepository;


    @Before
    public void setUp() throws Exception {
        var modelMapper = new ModelMapper();
        var catalogMapper =new CatalogMapper(modelMapper);
        service = new CatalogService(catalogRepository, catalogMapper);

        CatalogEntity catalogEntity = new CatalogEntity();
        catalogEntity.setId("id");
        catalogEntity.setCode("code");
        when(catalogRepository.findByCode(any(String.class))).thenReturn(catalogEntity);
    }

}