模拟服务方法调用(从内部 forEach 循环)时抛出 NullPointerException,实现此模拟的正确方法是什么?
NullPointerException thrown when Mocking a service method call (from an internal forEach loop), what is the correct way to implement this mock?
我有一个非常基本的 Spring 启动应用程序。
它由一个Controller
一个Service
和一个Repository
.
组成
我想测试基本上只调用 Service
的 Controller
,后者又调用 Repository
.
我试图模拟 Service
并将其注入 Controller
进行测试,但在这一行失败:
when(service.findEntry(1L)).thenReturn(model);
错误是 NullPointerException
,当我查看堆栈跟踪时,这是因为 HashSet.forEach()
调用。
我怎样才能解决这个问题?在上面的行中,方法 service.findEntry(1L)
实际上被调用并抛出一个 NullPointerException
错误:
java.lang.NullPointerException
at com.efl.journal.controller.JournalEntryControllerITests.testGetJournalEntryById(JournalEntryControllerITests.java:46)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.mockito.internal.runners.DefaultInternalRunner.evaluate(DefaultInternalRunner.java:44)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=11=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:74)
at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:80)
at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:39)
at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
我的测试Class:
package com.efl.journal.controller;
import com.efl.journal.model.JournalEntryModel;
import com.efl.journal.service.JournalEntryService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.boot.test.mock.mockito.MockBean;
import javax.ws.rs.core.Response;
import java.util.HashSet;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class JournalEntryControllerITests {
@MockBean
JournalEntryService service;
@InjectMocks
JournalEntryController controller;
@Test
public void testGetJournalEntryById(){
JournalEntryModel model = new JournalEntryModel();
model.setEntryId(1L);
model.setSubject("test 1");
model.setBody("This is a test");
model.setUserId(1L);
model.setTags(new HashSet<>());
when(service.findEntry(1L)).thenReturn(model);
Response result = controller.getJournalEntry(1L);
JournalEntryModel resultModel = (JournalEntryModel) result.getEntity();
assertEquals(model.getEntryId(), resultModel.getEntryId());
}
}
我的Controller
:
package com.efl.journal.controller;
import com.efl.journal.model.JournalEntryModel;
import com.efl.journal.service.JournalEntryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.ws.rs.core.Response;
import java.util.List;
@RestController
@RequestMapping("/")
@Slf4j
public class JournalEntryController {
JournalEntryService entryService;
@Autowired
public JournalEntryController(JournalEntryService entryService){
this.entryService = entryService;
}
@GetMapping("entry/{entryId}")
public Response getJournalEntry(@PathVariable Long entryId){
JournalEntryModel journalEntry = entryService.findEntry(entryId);
if( journalEntry != null){
return Response.status(Response.Status.OK)
.entity(journalEntry)
.build();
}
return Response.status(Response.Status.NO_CONTENT)
.build();
}
@GetMapping("entry")
public Response getAllJournalEntires(){
List<JournalEntryModel> entryList = entryService.findAllEntryObjects();
if(entryList.isEmpty()){
return Response.status(Response.Status.NO_CONTENT).build();
}
return Response.status(Response.Status.OK).entity(entryList).build();
}
@PostMapping("entry")
public Response postJournalEntry(@RequestBody JournalEntryModel entry) {
entry = entryService.saveJournalEntry(entry);
return Response.status(Response.Status.OK)
.entity(entry)
.build();
}
@DeleteMapping("entry/{entryId}")
public Response deleteJournalEntry(@PathVariable Long entryId, @RequestBody JournalEntryModel entry ){
entryService.deleteJournalEntry(entry);
return Response.status(Response.Status.NO_CONTENT)
.build();
}
}
我的 Service
class:
package com.efl.journal.service;
import com.efl.journal.entity.JournalEntry;
import com.efl.journal.model.JournalEntryModel;
import com.efl.journal.repository.JournalEntryRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
@Service
public class JournalEntryService {
private JournalEntryRepository journalEntryRepo;
@Autowired
public JournalEntryService(JournalEntryRepository journalEntryRepo){
this.journalEntryRepo = journalEntryRepo;
}
public JournalEntryModel findEntry(Long id) {
Optional<JournalEntry> optional = journalEntryRepo.findById(id);
if(optional.isPresent()){
return optional.get().toModel();
}
return null;
}
public List<JournalEntryModel> findAllEntryObjects(){
Iterable<JournalEntry> iterable = journalEntryRepo.findAll();
Iterator iterator = iterable.iterator();
List<JournalEntryModel> list = new ArrayList<>();
iterator.forEachRemaining(e -> {
list.add(((JournalEntry) e).toModel());
});
return list;
}
public JournalEntryModel saveJournalEntry(JournalEntryModel entry){
return journalEntryRepo.save(entry.toEntity()).toModel();
}
public void deleteJournalEntry(JournalEntryModel entry){
journalEntryRepo.delete(entry.toEntity());
}
public List<JournalEntryModel> findEntriesbyUserId(Long userId){
return null;
}
}
我的 JournalEntity class:
package com.efl.journal.entity;
import com.efl.journal.model.JournalEntryModel;
import com.efl.journal.model.TagModel;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.CreationTimestamp;
import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "entry")
@Getter
@Setter
public class JournalEntry implements Serializable {
@GeneratedValue
@Id
private Long entryId;
private Long userId;
private String subject;
private String body;
@CreationTimestamp
@ColumnDefault("CURRENT_TIMESTAMP")
private LocalDateTime localDateTime;
@ManyToMany(cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
@JoinTable(
name = "journal_entry_tags",
joinColumns = {@JoinColumn(name="entry_id")},
inverseJoinColumns = {@JoinColumn(name= "tag_id")}
)
private Set<Tag> tags = new HashSet<>();
public JournalEntry(){
}
public JournalEntryModel toModel(){
JournalEntryModel entry = new JournalEntryModel();
entry.setEntryId(entryId);
entry.setUserId(userId);
entry.setSubject(subject);
entry.setBody(body);
entry.setLocalDateTime(localDateTime);
Set<TagModel> tagModels = new HashSet<>();
tags.forEach(t->{
tagModels.add(t.toModel());
});
entry.setTags(tagModels);
return entry;
}
}
我认为错误发生在调用JournalEntity.toModel()
方法时。但是我不明白为什么当它是我试图模拟的方法中的代码的一部分时实际上调用了这个方法。
您的测试是单元测试,运行 使用 Mockito 运行ner,它根本不关心 Spring MockBean 注释。它应该用 Mockito 的 @Mock
方法注释,而不是 @MockBean
.
我有一个非常基本的 Spring 启动应用程序。
它由一个Controller
一个Service
和一个Repository
.
组成
我想测试基本上只调用 Service
的 Controller
,后者又调用 Repository
.
我试图模拟 Service
并将其注入 Controller
进行测试,但在这一行失败:
when(service.findEntry(1L)).thenReturn(model);
错误是 NullPointerException
,当我查看堆栈跟踪时,这是因为 HashSet.forEach()
调用。
我怎样才能解决这个问题?在上面的行中,方法 service.findEntry(1L)
实际上被调用并抛出一个 NullPointerException
错误:
java.lang.NullPointerException
at com.efl.journal.controller.JournalEntryControllerITests.testGetJournalEntryById(JournalEntryControllerITests.java:46)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.mockito.internal.runners.DefaultInternalRunner.evaluate(DefaultInternalRunner.java:44)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=11=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:74)
at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:80)
at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:39)
at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
我的测试Class:
package com.efl.journal.controller;
import com.efl.journal.model.JournalEntryModel;
import com.efl.journal.service.JournalEntryService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.boot.test.mock.mockito.MockBean;
import javax.ws.rs.core.Response;
import java.util.HashSet;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class JournalEntryControllerITests {
@MockBean
JournalEntryService service;
@InjectMocks
JournalEntryController controller;
@Test
public void testGetJournalEntryById(){
JournalEntryModel model = new JournalEntryModel();
model.setEntryId(1L);
model.setSubject("test 1");
model.setBody("This is a test");
model.setUserId(1L);
model.setTags(new HashSet<>());
when(service.findEntry(1L)).thenReturn(model);
Response result = controller.getJournalEntry(1L);
JournalEntryModel resultModel = (JournalEntryModel) result.getEntity();
assertEquals(model.getEntryId(), resultModel.getEntryId());
}
}
我的Controller
:
package com.efl.journal.controller;
import com.efl.journal.model.JournalEntryModel;
import com.efl.journal.service.JournalEntryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.ws.rs.core.Response;
import java.util.List;
@RestController
@RequestMapping("/")
@Slf4j
public class JournalEntryController {
JournalEntryService entryService;
@Autowired
public JournalEntryController(JournalEntryService entryService){
this.entryService = entryService;
}
@GetMapping("entry/{entryId}")
public Response getJournalEntry(@PathVariable Long entryId){
JournalEntryModel journalEntry = entryService.findEntry(entryId);
if( journalEntry != null){
return Response.status(Response.Status.OK)
.entity(journalEntry)
.build();
}
return Response.status(Response.Status.NO_CONTENT)
.build();
}
@GetMapping("entry")
public Response getAllJournalEntires(){
List<JournalEntryModel> entryList = entryService.findAllEntryObjects();
if(entryList.isEmpty()){
return Response.status(Response.Status.NO_CONTENT).build();
}
return Response.status(Response.Status.OK).entity(entryList).build();
}
@PostMapping("entry")
public Response postJournalEntry(@RequestBody JournalEntryModel entry) {
entry = entryService.saveJournalEntry(entry);
return Response.status(Response.Status.OK)
.entity(entry)
.build();
}
@DeleteMapping("entry/{entryId}")
public Response deleteJournalEntry(@PathVariable Long entryId, @RequestBody JournalEntryModel entry ){
entryService.deleteJournalEntry(entry);
return Response.status(Response.Status.NO_CONTENT)
.build();
}
}
我的 Service
class:
package com.efl.journal.service;
import com.efl.journal.entity.JournalEntry;
import com.efl.journal.model.JournalEntryModel;
import com.efl.journal.repository.JournalEntryRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
@Service
public class JournalEntryService {
private JournalEntryRepository journalEntryRepo;
@Autowired
public JournalEntryService(JournalEntryRepository journalEntryRepo){
this.journalEntryRepo = journalEntryRepo;
}
public JournalEntryModel findEntry(Long id) {
Optional<JournalEntry> optional = journalEntryRepo.findById(id);
if(optional.isPresent()){
return optional.get().toModel();
}
return null;
}
public List<JournalEntryModel> findAllEntryObjects(){
Iterable<JournalEntry> iterable = journalEntryRepo.findAll();
Iterator iterator = iterable.iterator();
List<JournalEntryModel> list = new ArrayList<>();
iterator.forEachRemaining(e -> {
list.add(((JournalEntry) e).toModel());
});
return list;
}
public JournalEntryModel saveJournalEntry(JournalEntryModel entry){
return journalEntryRepo.save(entry.toEntity()).toModel();
}
public void deleteJournalEntry(JournalEntryModel entry){
journalEntryRepo.delete(entry.toEntity());
}
public List<JournalEntryModel> findEntriesbyUserId(Long userId){
return null;
}
}
我的 JournalEntity class:
package com.efl.journal.entity;
import com.efl.journal.model.JournalEntryModel;
import com.efl.journal.model.TagModel;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.CreationTimestamp;
import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "entry")
@Getter
@Setter
public class JournalEntry implements Serializable {
@GeneratedValue
@Id
private Long entryId;
private Long userId;
private String subject;
private String body;
@CreationTimestamp
@ColumnDefault("CURRENT_TIMESTAMP")
private LocalDateTime localDateTime;
@ManyToMany(cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
@JoinTable(
name = "journal_entry_tags",
joinColumns = {@JoinColumn(name="entry_id")},
inverseJoinColumns = {@JoinColumn(name= "tag_id")}
)
private Set<Tag> tags = new HashSet<>();
public JournalEntry(){
}
public JournalEntryModel toModel(){
JournalEntryModel entry = new JournalEntryModel();
entry.setEntryId(entryId);
entry.setUserId(userId);
entry.setSubject(subject);
entry.setBody(body);
entry.setLocalDateTime(localDateTime);
Set<TagModel> tagModels = new HashSet<>();
tags.forEach(t->{
tagModels.add(t.toModel());
});
entry.setTags(tagModels);
return entry;
}
}
我认为错误发生在调用JournalEntity.toModel()
方法时。但是我不明白为什么当它是我试图模拟的方法中的代码的一部分时实际上调用了这个方法。
您的测试是单元测试,运行 使用 Mockito 运行ner,它根本不关心 Spring MockBean 注释。它应该用 Mockito 的 @Mock
方法注释,而不是 @MockBean
.