ModelMapper在使用JUnit和Mockito时抛出空指针异常(NPE)。

huangapple go评论76阅读模式
英文:

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(&quot;id&quot;);
catalogEntity.setCode(&quot;code&quot;);
catalogEntity.setType(&quot;type&quot;);
catalogEntity.setValue(&quot;value&quot;);
//        Optional&lt;CatalogEntity&gt; optionalCatalog = Optional.of(catalogEntity);
when(catalogRepository.findByCode(any(String.class))).thenReturn(catalogEntity);
}
@Test
public void whenFindByCode() {
//Act
CatalogDto myCatalogDto = service.findByCode(&quot;code&quot;);
//Assert
assertTrue(myCatalogDto.getCode().equals(&quot;code&quot;));
}
}

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&lt;CatalogDto&gt; findByType(String type) {
LOGGER.info(&quot;Getting catalogs by type {}&quot;, type);
List&lt;CatalogEntity&gt; catalogsEntityList = catalogRepository.findByType(type);
List&lt;CatalogDto&gt; catalogDtoList = new ArrayList&lt;&gt;();
catalogsEntityList.forEach(catalogEntity -&gt; {
catalogDtoList.add(catalogMapper.convertCatalogEntityToCatalogDto(catalogEntity));
});
return catalogDtoList;
}
/**
* Find catalog by code.
* @param code
* @return catalog
*/
public CatalogDto findByCode(String code) {
LOGGER.info(&quot;Getting catalogs by code {}&quot;, 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&lt;CatalogEntity, String&gt; {
List&lt;CatalogEntity&gt; 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.
ModelMapper在使用JUnit和Mockito时抛出空指针异常(NPE)。
ModelMapper在使用JUnit和Mockito时抛出空指针异常(NPE)。

答案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(&quot;id&quot;);
        catalogEntity.setCode(&quot;code&quot;);
        when(catalogRepository.findByCode(any(String.class))).thenReturn(catalogEntity);
    }

}

huangapple
  • 本文由 发表于 2020年9月30日 04:32:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/64127228.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定