英文:
ModelMapper throws NPE using JUnit Mockito
问题
以下是您提供的代码部分的翻译:
我在使用 ModelMapper 时遇到了空指针异常(NPE)
CatalogServiceTest
@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"));
}
}
CatalogService
@Service
public class CatalogService {
private static final Logger LOGGER = LoggerFactory.getLogger(CatalogService.class);
@Autowired
CatalogRepository catalogRepository;
@Autowired
CatalogMapper catalogMapper;
/**
*
* @param type
* @return 类型为 type 的目录对象
*/
public List<CatalogDto> findByType(String type) {
LOGGER.info("通过类型 {} 获取目录", type);
List<CatalogEntity> catalogsEntityList = catalogRepository.findByType(type);
List<CatalogDto> catalogDtoList = new ArrayList<>();
catalogsEntityList.forEach(catalogEntity -> {
catalogDtoList.add(catalogMapper.convertCatalogEntityToCatalogDto(catalogEntity));
});
return catalogDtoList;
}
/**
* 按代码查找目录。
* @param code
* @return 目录
*/
public CatalogDto findByCode(String code) {
LOGGER.info("通过代码 {} 获取目录", code);
return catalogMapper.convertCatalogEntityToCatalogDto(catalogRepository.findByCode(code));
}
}
CatalogMapper
@Component
public class CatalogMapper {
@Autowired
private ModelMapper modelMapper;
/**
* 将 CatalogEntity 对象转换为 CatalogDto 对象
* @param catalogEntity
* @return 转换后的 CatalogDto 对象
*/
public CatalogDto convertCatalogEntityToCatalogDto(CatalogEntity catalogEntity) {
return modelMapper.map(catalogEntity, CatalogDto.class);
}
}
CatalogRepository
@Repository
public interface CatalogRepository extends MongoRepository<CatalogEntity, String> {
List<CatalogEntity> findByType(String type);
CatalogEntity findByCode(String code);
}
问题
====
`catalogRepository.findByCode(code)` 如预期地返回 CatalogEntity 对象,问题出现在执行 `catalogMapper.convertCatalogEntityToCatalogDto(catalogRepository.findByCode(code));` 后,该语句返回了 null。
我在使用 IntelliJ,这是在执行 `catalogMapper.convertCatalogEntityToCatalogDto` 函数之前的断点位置。
[![在这里输入图片描述][1]][1]
[![在这里输入图片描述][2]][2]
[1]: https://i.stack.imgur.com/GR4pb.png
[2]: https://i.stack.imgur.com/NdafM.png
英文:
I'm having a NPE using ModelMapper
CatalogServiceTest
@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"));
}
}
CatalogService
@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));
}
}
CatalogMapper
@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);
}
}
CatalogRepository
@Repository
public interface CatalogRepository extends MongoRepository<CatalogEntity, String> {
List<CatalogEntity> findByType(String type);
CatalogEntity findByCode(String code);
}
The problem
The catalogRepository.findByCode(code)
is returning a CatalogEntity object as expected and the problem comes after executing catalogMapper.convertCatalogEntityToCatalogDto(catalogRepository.findByCode(code));
that return null.
I'm using Intellij and this is the breakpoint just right before execute catalogMapper.convertCatalogEntityToCatalogDto
function.
答案1
得分: 1
catalogMapper是一个没有存根方法的模拟对象。
有几种方法可以修复你的测试:
选项1:仅测试与CatalogMapper的交互
在这个选项中,你会存根调用catalogMapper.convertCatalogEntityToCatalogDto
。这是一个薄薄的单元测试,你只测试与协作服务的交互。
正如你所说,你想测试映射器的真实实现,有两个选项:
选项2:使用SpringBootTest
在这个选项中,你依赖于SpringBootTest来设置整个应用程序上下文。
你需要进行以下更改:
- 使用
@Autowired
替代@InjectMock
来获取你要测试的对象 - 使用
@MockBean
替代@Mock
用于仓库。SpringBootTest
会忽略@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);
}
}
英文:
The catalogMapper is a mock with no stubbed methods.
There are a few ways in which you can fix your test:
Option 1: only test interaction with CatalogMapper
In this option, you stub a call to catalogMapper.convertCatalogEntityToCatalogDto
. This is a thin unit test, you only test interactions with collaborating services.
As you said you want to test real implementation of mapper, there are two options:
Option 2: use SpringBootTest
In this option, you rely on SpringBootTest to set up your entire application context.
You need following changes:
- use
@Autowired
instead of@InjectMock
to get you object under test - use
@MockBean
instead of@Mock
for repository.SpringBootTest
ignores@Mock
. - get rid of other mocks
- as it creates entire application context, I would exclude this option, unless a full integration test is your goal (you started with
@SpringBootTest
in your code)
@SpringBootTest
public class CatalogServiceTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Autowired
private CatalogService service;
@MockBean
CatalogRepository catalogRepository;
}
Option 3: construct services you need yourself
- get rid of
@SpringBootTest
- mock only objects you want to mock - the repository
- create real objects for other services
- you may need to change field injection to constructor injection in your services, which is a good idea anyway
@Service
public class CatalogService {
final CatalogRepository catalogRepository;
final CatalogMapper catalogMapper;
@Autowired
public CatalogService(CatalogRepository catalogRepository, CatalogMapper catalogMapper) {
this.catalogRepository = catalogRepository;
this.catalogMapper = catalogMapper;
}
}
This approach creates only objects that are used by your test, not entire application context, so will likely result in leaner test that option 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);
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论