java - 使用 Mockito 和 jUnit5 的 Spring Boot Controller 测试失败,因为 Spring 无法创建加载属性的 Property Service 类的 bean?

标签 java hibernate spring-boot mockito junit5

我有一个简单的 spring boot 项目--

这是项目结构-

enter image description here

如果我运行我的 spring boot 应用程序,它运行良好,没有任何错误。我能够通过其余 Controller 方法获取所有客户、获取单个客户、删除客户和添加客户。

通过 Postman 我可以添加客户--

<Customer>
<firstName>TestData</firstName>
<lastName>Test</lastName>
<gender>M</gender>
<date>2020-01-26T09:00:00.000+0000</date>
<authId>6AE-BH3-24F-67FG-76G-345G-AGF6H</authId>
<addressdto>
<city>Test City</city>
<country>Test Country</country>
</addressdto>
</Customer>

响应

成功添加34位客户

这意味着当应用程序启动时,它能够实例化 PropertyService.java。 因此,我能够通过 PropertyService.java 访问存在于我的 application-dev.properties 中的身份验证 ID。我的 src/test/resources-> application.properties 中存在相同的属性。

有两个问题--

  1. 现在,当我将 HomeControllerTest.java 类作为jUnit 测试 运行时,我 我收到一个错误。我调试并找出了根本原因 错误。在我的 HomeController.java 类中,它无法 实例化 PropertyService.java 类,所以我得到一个 出现空指针异常,测试类继续执行失败。
  2. 我无法通过我的测试类中的 PropertyService.java 访问 authId。所以我不得不硬编码。

谁能告诉我为什么会遇到这个问题?我该如何解决?

HomeController.java

@PostMapping("/customer")
    public ResponseEntity<String> addCustomer(@RequestBody CustomerDto customerDto) {
        String message = "";
        ResponseEntity<String> finalMessage = null;
        try {
            if ((!customerDto.getAuthId().equals(propertyService.getKeytoAddCustomer()))) {
                System.out.println("If check failed: "+propertyService.getKeytoAddCustomer());
                System.out.println("Unauthorized access attempted");
                message = "Unauthorized access attempted";
                finalMessage = new ResponseEntity<>(message, HttpStatus.UNAUTHORIZED);
            }
            System.out.println("If check passed :"+propertyService.getKeytoAddCustomer());

            Customer customer = mapper.mapToEntity(customerDto);
            customerService.addCustomer(customer);
            message = "Customer with " + customer.getId() + " sucessfully added";
            finalMessage = new ResponseEntity<>(message, HttpStatus.OK);

        } catch (Exception e) {
            message = "Failed to add customer due to " + e.getMessage();
            finalMessage = new ResponseEntity<>(message, HttpStatus.INTERNAL_SERVER_ERROR);
        }
        return finalMessage;
    }

PS- equals(propertyService.getKeytoAddCustomer()))(问题 1)--> 这里我遇到了空指针异常

PropertyService.java

package com.spring.liquibase.demo.utility;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;

@Configuration
@PropertySource("classpath:config.properties")
public class PropertyService {
    @Autowired
    private Environment env;

    public String getKeytoAddCustomer() {
        return env.getProperty("auth.key.to.add.customer");
    }
}

HomeControllerTest.java


@ExtendWith(SpringExtension.class)
class HomeControllerTest {
    private MockMvc mvc;

    @InjectMocks
    private HomeController homeController;

    @MockBean
    private CustomerService customerService;
//
//  @Autowired
//  private PropertyService propertyService;

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

    @Test
    public void testaddCustomer() throws Exception {
        String uri = "/customer";
        CustomerDto custDto = this.mockCustomerObject();
        String actualResult = mvc
                .perform(MockMvcRequestBuilders.post(uri).contentType(MediaType.APPLICATION_JSON)
                        .content(asJsonString(custDto)))
                .andExpect(MockMvcResultMatchers.status().isOk()).andReturn().getResponse().getContentAsString();
        Assertions.assertEquals(actualResult, "Customer with " + custDto.getId() + " sucessfully added");
    }

    private CustomerDto mockCustomerObject() {
        CustomerDto cusDto = new CustomerDto();
        AddressDto addressDto = new AddressDto();
        addressDto.setCity("BBSR");
        addressDto.setCountry("INDIA");
        cusDto.setDate(new Date());
        cusDto.setFirstName("Biprojeet");
        cusDto.setLastName("KAR");
        cusDto.setGender("M");
        cusDto.setAuthId(" 6AE-BH3-24F-67FG-76G-345G-AGF6H");
        cusDto.setAddressdto(addressDto);
        return cusDto;

    }

    public static String asJsonString(CustomerDto cusDto) {
        try {
            return new ObjectMapper().writeValueAsString(cusDto);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

PS- 我已经注释掉了代码,因为我无法访问这里的 Prop 文件。这里也需要帮助(问题 2)

application.properties-- 在 src/test/resources 中

# DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url=jdbc:mysql******useSSL=false
spring.datasource.username=****
spring.datasource.password=****

# Hibernate
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update
logging.level.org.springframework.web=INFO
logging.level.com=DEBUG

customer.auth.key = 6AE-BH3-24F-67FG-76G-345G-AGF6H

应用程序开发属性

same as above

application.properties inside->src/main/resources

spring.profiles.active=dev
logging.level.org.springframework.web=INFO
logging.level.com=DEBUG
server.port=8080

jUnit 错误日志

java.lang.AssertionError: Status expected:<200> but was:<500>
    at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
    at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122)
    at org.springframework.test.web.servlet.result.StatusResultMatchers.lambda$matcher$9(StatusResultMatchers.java:627)
    at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:196)
    at com.spring.liquibase.demo.controller.HomeControllerTest.testaddCustomer(HomeControllerTest.java:50)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.base/java.lang.reflect.Method.invoke(Unknown Source)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:436)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:170)
    at org.junit.jupiter.engine.execution.ThrowableCollector.execute(ThrowableCollector.java:40)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:166)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:113)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:58)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:112)
    at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$2(HierarchicalTestExecutor.java:120)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(Unknown Source)
    at java.base/java.util.stream.ReferencePipeline$2$1.accept(Unknown Source)
    at java.base/java.util.Iterator.forEachRemaining(Unknown Source)
    at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Unknown Source)
    at java.base/java.util.stream.AbstractPipeline.copyInto(Unknown Source)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)
    at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(Unknown Source)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(Unknown Source)
    at java.base/java.util.stream.AbstractPipeline.evaluate(Unknown Source)
    at java.base/java.util.stream.ReferencePipeline.forEach(Unknown Source)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:120)
    at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$2(HierarchicalTestExecutor.java:120)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(Unknown Source)
    at java.base/java.util.stream.ReferencePipeline$2$1.accept(Unknown Source)
    at java.base/java.util.Iterator.forEachRemaining(Unknown Source)
    at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Unknown Source)
    at java.base/java.util.stream.AbstractPipeline.copyInto(Unknown Source)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)
    at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(Unknown Source)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(Unknown Source)
    at java.base/java.util.stream.AbstractPipeline.evaluate(Unknown Source)
    at java.base/java.util.stream.ReferencePipeline.forEach(Unknown Source)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:120)
    at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:55)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:43)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:170)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:154)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:90)
    at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:89)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)

最佳答案

完成你的 repo 后,这里是最终代码

@WebMvcTest(HomeController.class)
class HomeControllerTest {

@Autowired
private MockMvc mvc;

@MockBean
private CustomerService customerService;

@MockBean
private PropertyService propertyService;

@MockBean
private EntityToDtoMapper mapper;


@Test
public void testaddCustomer() throws Exception {
    String uri = "/customer";
    CustomerDto custDto = this.mockCustomerObject();
    Customer customer = getCustomerEntity();
    Mockito.when(mapper.mapToEntity(Mockito.any(CustomerDto.class))).thenReturn(customer);
    String actualResult = mvc
            .perform(MockMvcRequestBuilders.post(uri).contentType(MediaType.APPLICATION_JSON)
                    .content(asJsonString(custDto)))
            .andExpect(MockMvcResultMatchers.status().isOk()).andReturn().getResponse().getContentAsString();
    Assertions.assertEquals(actualResult, "Customer with " + custDto.getId() + " sucessfully added");
}

private CustomerDto mockCustomerObject() {
    CustomerDto cusDto = new CustomerDto();
    AddressDto addressDto = new AddressDto();
    addressDto.setCity("BBSR");
    addressDto.setCountry("INDIA");
    cusDto.setDate(new Date());
    cusDto.setFirstName("Biprojeet");
    cusDto.setLastName("KAR");
    cusDto.setGender("M");
    cusDto.setAuthId(" 6AE-BH3-24F-67FG-76G-345G-AGF6H");
    cusDto.setAddressdto(addressDto);
    return cusDto;

}


private Customer getCustomerEntity() {
    Customer customer = new Customer();
    Address address = new Address();
    address.setCity("BBSR");
    address.setCountry("INDIA");
    customer.setDate(new Date());
    customer.setFirstName("Biprojeet");
    customer.setLastName("KAR");
    customer.setGender("M");
    customer.setAddress(address);
    return customer;

}

public static String asJsonString(CustomerDto cusDto) {
    try {
        return new ObjectMapper().writeValueAsString(cusDto);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

这里的问题是您混淆了概念。在您的实现中,您尝试进行单元测试但期望集成行为。

当您使用 Spring boot 及其测试入门工具包时,它带有 JUnit 和 Mockito 等框架的依赖项,您可以使用 mockitio 框架轻松模拟那些抛出 Null 指针异常的类和方法,因为服务器未运行并且IOC 容器未启动,这就是它们为 NULL 的原因。

所以在您的代码中,CustomerService、PropertyService 和 EntityToDtoMapper 为 NULL。

所以这里的问题是我们如何在不启动服务器的情况下上传 spring 应用程序上下文。

可以通过两种方式完成,或者使用@SpringBootTest 和@AutoConfigureMockMvc 注释加载整个 spring 应用程序上下文。

或者我们可以通过使用@WebMvcTest 注解来缩小 Controller 本身的 spring 应用程序上下文

所以我在这里使用的解决方案是仅通过使用@WebMvcTest(HomeController.class) 注释将测试范围缩小到 Controller 。

但是那些 CustomerService、PropertyService 和 EntityToDtoMapper 仍然是 NULL。因此,要模拟这些类,我们可以使用 @Mock 或 @MockBean 注释,但这些注释之间存在细微差别

The Mockito.mock() method allows us to create a mock object of a class or an interface and the @MockBean to add mock objects to the Spring application context. The mock will replace any existing bean of the same type in the application context.

因此,由于我们已经为 Controller 上传了 spring 应用程序上下文,所以 Controller 也在应用程序上下文中期待这些 bean,这可以通过 @MockBean 注释来实现。

在模拟所有这些 bean 之后,您的 Controller bean 将被创建,但是有些方法您需要一些返回值,因此您必须在代码中编写预期的返回值,这可以像那样完成

Mockito.when(mapper.mapToEntity(Mockito.any(CustomerDto.class))).thenReturn(customer);

如果你错过了这个特定的步骤,那么在 Controller 中你将在这行代码中得到一个 NULL 指针异常

message = "Customer with " + customer.getId() + " sucessfully added";

因为你会写代码

Customer customer = mapper.mapToEntity(customerDto);

将返回 NULL。

我希望这会帮助并激励您获得更多关于这些概念的知识。

如果需要进一步的帮助,请告诉我

关于java - 使用 Mockito 和 jUnit5 的 Spring Boot Controller 测试失败,因为 Spring 无法创建加载属性的 Property Service 类的 bean?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61755122/

相关文章:

java - 使用 Chef 通过代理在 Raspberry PI 3 上安装 Open JDK 8

java - QueryDsl,结合标准 JPA 和 Hibernate 的类级 @Where 注释?

hibernate - 从 ehcache 加载 hibernate 对象时出现延迟初始化异常

java - Spring security 有时会在成功登录后重定向到默认页面而不是预登录页面

java - 如何在一个简单的java程序中替换spring属性占位符

java - 暂停 Swing worker

java - 如何使用 java 访问 Salesforce 附件正文(base64 二进制数据)?

java - 如何将表与该表的实体类中 View 的一列连接起来

spring-boot - 当我尝试创建新的 Spring Boot 项目时,无法实例化 SLF4J LoggerFactory

java - SimpleIdentificResourceAssembler 无法解析为类型