当模拟服务方法调用时(从内部forEach循环)抛出NullPointerException,实现此模拟的正确方法是什么?

问题描述 投票:0回答:2

我有一个非常基本的Spring Boot应用程序。它由ControllerServiceRepository组成。我要测试基本上只调用ControllerService,后者依次调用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$1.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$1$1.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$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.mockito.internal.runners.DefaultInternalRunner$1.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)

我的测试班级:

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类:

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类:

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()方法而发生的。但是我不明白为什么当我尝试模拟的方法中的一部分代码时实际上会调用此方法。

java spring-boot unit-testing testng junit4
2个回答
0
投票

您的测试是一个单元测试,由Mockito运行器运行,它根本不关心Spring MockBean批注。应该用Mockito的@Mock方法注释,而不是@MockBean


-1
投票

当由Mockito管理的service被调用时

when(service.findEntry(1L)).thenReturn(model);

存储库journalEntryRepo尚未被注入,因此是null

public JournalEntryModel findEntry(Long id) {
    Optional<JournalEntry> optional = journalEntryRepo.findById(id);
    ...
© www.soinside.com 2019 - 2024. All rights reserved.