繁体   English   中英

SpringBoot Controller 测试使用 Mockito 和 jUnit5 失败,因为 Spring 无法创建 PropertyService ZA2F2ED4F8EBC40ABCC1ZC 的 bean

[英]SpringBoot Controller test using Mockito & jUnit5 failing as Spring is unable to create bean of PropertyService class which loads properties?

我有一个简单的 spring 引导项目——

这是项目结构-

在此处输入图像描述

如果我运行我的 spring 启动应用程序,它运行良好,没有任何错误。 我能够通过我的 rest 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>

回复

Customer with 34 sucessfully added

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

有两个问题——

  1. 现在,当我将HomeControllerTest.java class 作为jUnit test运行时,我遇到了错误。 我调试并找到了错误的根本原因。 Inside my HomeController.java class, it is unable to instantiate the PropertyService.java class, so i am getting a null pointer exception the further execution of test class failed.
  2. 在我的测试 class 中,我无法通过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)-> 在这里我得到null pointer exception

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");
    }
}

主页控制器Test.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-我已经注释掉了代码,因为我无法在这里访问道具文件。这里也需要帮助(问题 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

应用程序-dev.properties

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)

完成您的回购后,这是最终代码

@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);
    }
}

}

这里的问题是您正在混合概念。 在您的实现中,您试图进行单元测试,但期待集成的行为。

As you been using Spring boot with it's test starter kit which comes with the dependencies of framework like JUnit and Mockito, You can easily mock those classes and methods which throwing the Null pointer exception by using mockitio framework because server is not running and IOC container is不起来,这就是为什么那些是 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. 模拟将替换应用程序上下文中任何现有的相同类型的 bean。

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

在 mocking 所有这些 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。

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

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

还有一个错误。 这是一个主要问题——即使我给出了错误的 authId,我的测试用例也通过了。 请在您当地尝试,如果您的 r 具有相同的行为,请告诉我。

偏离课程,您的测试将通过,因为您的代码仅检查不阻止代码执行的条件,请参阅下面的代码:

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);

    }

在 if 条件下,您只执行代码块,然后指示执行 if 条件之外的代码块。

所以在 if 条件下它什么也不做。 所以你必须根据你的预期行为改进你的代码。

如果您想阻止代码执行,请参考以下代码

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";
            return 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();
        e.printStackTrace();
        finalMessage = new ResponseEntity<>(message, HttpStatus.INTERNAL_SERVER_ERROR);
    }

在这里,我在 If 条件下设置了 return 语句。

但是,如果您想在比较消息的当前测试用例中失败,请参考以下代码:

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);
        }else {
            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();
        e.printStackTrace();
        finalMessage = new ResponseEntity<>(message, HttpStatus.INTERNAL_SERVER_ERROR);
    }

在这里,您只需要在 else 部分下设置 if 条件之外的代码块。

使用@MockBean@Mock尝试 mocking PropertyService。

我注意到您在 class 定义上方缺少@WebMvcTest(Controller.class) ,您需要对 Mvc 控制器进行单元测试。 解释

如果@MockBean不起作用。

尝试:

使用Mockito.when() ,您可以简单地返回所需/预期的结果以及何时调用所需的方法。

使用Mockito.verify()来确保执行时的愿望。

when(propertyService.getKeytoAddCustomer()).thenReturn("Desired String");

when(customerService.addCustomer(customerObject)).thenReturn("Desired result");

//DO mvc.perform(...);

verify(propertyService).getKeytoAddCustomer();

verify(customerService).addCustomer(customerObject());

问题二

我假设属性文件的问题是因为您使用的是spring.profile.active=dev但属性文件是 test/resources 是application.properties而不是application-dev.properties尽管它是 test/ 中唯一的属性文件资源。 将两个资源文件夹中的文件重命名为完全相同,看看会发生什么。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM