Spring Boot mockmvc 中的 Mockito 存根出现 UnnecessaryStubbingException,并且 MvcResult 结果不包含模拟值

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

我正在编写单元测试来测试 REST API 端点。我使用 MockMvc 来处理 API 测试,使用 @InjectMocks 来加载端点,使用 @Mock 来模拟服务层。以下是片段。

图书控制器

@RestController
@RequestMapping( path="api/v1/book")
public class BookController {

    private final BookService bookService;

    @Autowired
    public BookController (BookService bookService){
        this.bookService = bookService;
    }

    @GetMapping
    public List<Book> getBooks() {
        return bookService.getAllBook();
    }
}

预订服务

@Service
public class BookService {
    private final BookRepository bookRepository;    

    @Autowired
    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    public List<Book> getAllBook() {
        return bookRepository.findAll();
    }
}

**图书存储库**

@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
    List<Book> findAll();
}

测试类

@ExtendWith(MockitoExtension.class)
public class BookControllerTest {
    private MockMvc mockMvc;
    @InjectMocks
    private BookController bookController;

    @Mock
    private BookService bookService;

    @BeforeEach
    public void setup() {
        MockitoAnnotations.initMocks(this);
         mockMvc = MockMvcBuilders.standaloneSetup(bookController).build();
    }

    @Test
    public void getAllRecords_success() throws Exception {
        List<Book> records = Arrays.asList(record1, record2, record3);
        Mockito.when(bookService.getAllBook()).thenReturn(records);
        MvcResult result = mockMvc.perform(MockMvcRequestBuilders
                        .get("/api/v1/book")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andDo(print())
                .andReturn();
    }
}

当我运行测试时,我得到了 UnnecessaryStubbingException。 MvcResult result 变量具有空块而不是三本书的块(records)。 所以,我相信原因是我可能不太了解如何使用 Mockito。你能指出我正确的方向吗?

SpringBoot版本为3.2.1。

非常感谢!

================================

更新#1

我发现如果我像这样改变控制器:

@RestController
@RequestMapping( path="api/v1/book")
public class BookController {

//    private final BookService bookService;
    BookService bookService;

//    @Autowired
//    public BookController (BookService bookService){
//        this.bookService = bookService;
//    }

    @GetMapping
    public List<Book> getBooks(){
        return bookService.getAllBooks();
    }
}

然后模拟起作用了并且MvcResult结果变量有大量的三本书(记录)。任何人都可以解释如何解决这个问题,因为使用更新的控制器,spring 应用程序在通过邮递员发送“/api/v1/book”端点上的 get 请求时返回 500 代码。控制台中有:

NullPointerException:无法调用“mypackage.BookService.getAllBooks()”,因为“this.bookService”为空

unit-testing mockito spring-boot-test
1个回答
0
投票

您正在初始化模拟两次:

  • 一次通过
    MockitoExtension
    (内部调用
    initMocks
    /
    openMocks
  • 一次通过显式调用
    initMocks

如果将模拟注入到最终字段,则双重初始化会失败

检查这个例子:

interface Foo {
}

class Bar {
    final Foo foo;

    Bar(Foo foo) {
        this.foo = foo;
    }
}

@ExtendWith(MockitoExtension.class)
public class InitMocksTest {
    @InjectMocks Bar bar;
    @Mock Foo foo;


    @BeforeEach
    void setup() {
        System.out.println("bar: " + defaultToString(bar));
        System.out.println("foo: " + defaultToString(foo));
        System.out.println("bar.foo: " + defaultToString(bar.foo));
        System.out.println();

        MockitoAnnotations.openMocks(this);
        System.out.println("bar: " + defaultToString(bar));
        System.out.println("foo: " + defaultToString(foo));
        System.out.println("bar.foo: " + defaultToString(bar.foo));
    }

    public static String defaultToString(Object o) {
        return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode());
    }

    @Test
    void dummy() {
    }

}

输出:

bar: com.so.examples.mockito.Bar@5d332969
foo: com.so.examples.mockito.Foo$MockitoMock$v4JhQAjO@6ca320ab
bar.foo: com.so.examples.mockito.Foo$MockitoMock$v4JhQAjO@6ca320ab

bar: com.so.examples.mockito.Bar@5d332969
foo: com.so.examples.mockito.Foo$MockitoMock$v4JhQAjO@1e34c607
bar.foo: com.so.examples.mockito.Foo$MockitoMock$v4JhQAjO@6ca320ab

第二次初始化后观察:

  • foo 获得新值
  • bar foo 保持旧值
  • foo 和 bar.foo 没有指向同一个对象
© www.soinside.com 2019 - 2024. All rights reserved.