Spring使用 Mockito 和 jUnit5 的启动控制器测试失败,因为 Spring 无法创建加载属性的 PropertyService class 的 bean?
SpringBoot Controller test using Mockito & jUnit5 failing as Spring is unable to create bean of PropertyService class which loads properties?
我有一个简单的spring启动项目--
这是项目结构-
如果我 运行 我的 spring 启动应用程序,它 运行 没问题,没有任何错误。我能够通过我的其余控制器方法获取所有客户、获取单个客户、删除客户和添加客户。
通过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
.
有两个问题--
- 现在当我 运行 我的
HomeControllerTest.java
class 为 jUnit test
时,我
我收到一个错误。我调试并找出了根本原因
错误。在我的 HomeController.java
class 里面,它无法
实例化 PropertyService.java
class,所以我得到一个
null pointer exception
there.Thus 进一步执行测试 class 失败。
- 我在测试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");
}
}
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- 我已经注释掉了代码,因为我无法访问道具文件 here.Need 在这里也有帮助(问题 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里面->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(StatusResultMatchers.java:627)
at org.springframework.test.web.servlet.MockMvc.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(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(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(HierarchicalTestExecutor.java:120)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(Unknown Source)
at java.base/java.util.stream.ReferencePipeline.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(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(HierarchicalTestExecutor.java:120)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(Unknown Source)
at java.base/java.util.stream.ReferencePipeline.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(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);
}
}
}
这里的问题是你混淆了概念。在您的实施中,您尝试进行单元测试但期望集成行为。
由于您一直在使用 Spring 启动及其测试入门工具包,该工具包附带 JUnit 和 Mockito 等框架的依赖项,您可以轻松地模拟那些 类 和抛出空指针异常的方法通过使用 mockitio 框架,因为服务器不是 运行 并且 IOC 容器未启动,这就是为什么它们为 NULL。
因此在您的代码中,CustomerService、PropertyService 和 EntityToDtoMapper 为 NULL。
所以这里的问题是我们如何在不启动服务器的情况下上传 spring 应用程序上下文。
可以通过两种方式完成,要么使用@SpringBootTest 和@AutoConfigureMockMvc 注释加载整个spring 应用程序上下文。
或者我们可以通过使用@WebMvcTest 注释
缩小 spring 应用程序上下文,仅针对控制器本身
所以我在这里使用的解决方案是仅通过使用 @WebMvcTest(HomeController.class) 注释将测试范围缩小到控制器。
但那些 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.
因此,由于我们已经为控制器上传了 spring 应用程序上下文,因此控制器也在应用程序上下文中期待这些 bean,这可以通过 @MockBean 注释来实现。
在模拟所有这些 bean 之后,您的控制器 bean 将被创建,但是有些方法您期望一些 return 值,因此您必须在代码中编写预期的 return 值,这可能是这样做
Mockito.when(mapper.mapToEntity(Mockito.any(CustomerDto.class))).thenReturn(customer);
如果你错过了这个特定的步骤,那么在控制器中你将在这行代码中得到一个 NULL 指针异常
message = "Customer with " + customer.getId() + " sucessfully added";
因为你编码
Customer customer = mapper.mapToEntity(customerDto);
将 return NULL。
我希望这会帮助并激励您获得更多关于这些概念的知识。
如果需要任何进一步的帮助,请告诉我
尝试使用 @MockBean
或 @Mock
模拟 PropertyService。
我注意到您在 class 定义之上缺少 @WebMvcTest(Controller.class)
,您需要对 Mvc 控制器进行单元测试。
Explanation
如果@MockBean
不起作用。
尝试:
使用 Mockito.when()
,您可以简单地 return 您的 desired/expected 结果以及何时调用所需的方法。
使用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/resources 中唯一的 属性 文件。将两个资源文件夹中的文件重命名为完全相同的文件,看看会发生什么。
还有一个错误。这是一个主要问题——即使我提供了错误的 authId ,我的测试用例也通过了。请在您当地尝试,如果您有相同的行为,请告诉我。
当然你的测试将通过,因为你的代码只是检查条件它不会阻止代码被执行,请参考下面的代码:
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条件之外的代码块即可。
我有一个简单的spring启动项目--
这是项目结构-
如果我 运行 我的 spring 启动应用程序,它 运行 没问题,没有任何错误。我能够通过我的其余控制器方法获取所有客户、获取单个客户、删除客户和添加客户。
通过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
.
有两个问题--
- 现在当我 运行 我的
HomeControllerTest.java
class 为jUnit test
时,我 我收到一个错误。我调试并找出了根本原因 错误。在我的HomeController.java
class 里面,它无法 实例化PropertyService.java
class,所以我得到一个null pointer exception
there.Thus 进一步执行测试 class 失败。 - 我在测试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");
}
}
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- 我已经注释掉了代码,因为我无法访问道具文件 here.Need 在这里也有帮助(问题 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里面->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(StatusResultMatchers.java:627)
at org.springframework.test.web.servlet.MockMvc.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(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(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(HierarchicalTestExecutor.java:120)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(Unknown Source)
at java.base/java.util.stream.ReferencePipeline.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(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(HierarchicalTestExecutor.java:120)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(Unknown Source)
at java.base/java.util.stream.ReferencePipeline.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(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);
}
}
}
这里的问题是你混淆了概念。在您的实施中,您尝试进行单元测试但期望集成行为。
由于您一直在使用 Spring 启动及其测试入门工具包,该工具包附带 JUnit 和 Mockito 等框架的依赖项,您可以轻松地模拟那些 类 和抛出空指针异常的方法通过使用 mockitio 框架,因为服务器不是 运行 并且 IOC 容器未启动,这就是为什么它们为 NULL。
因此在您的代码中,CustomerService、PropertyService 和 EntityToDtoMapper 为 NULL。
所以这里的问题是我们如何在不启动服务器的情况下上传 spring 应用程序上下文。
可以通过两种方式完成,要么使用@SpringBootTest 和@AutoConfigureMockMvc 注释加载整个spring 应用程序上下文。
或者我们可以通过使用@WebMvcTest 注释
缩小 spring 应用程序上下文,仅针对控制器本身所以我在这里使用的解决方案是仅通过使用 @WebMvcTest(HomeController.class) 注释将测试范围缩小到控制器。
但那些 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.
因此,由于我们已经为控制器上传了 spring 应用程序上下文,因此控制器也在应用程序上下文中期待这些 bean,这可以通过 @MockBean 注释来实现。
在模拟所有这些 bean 之后,您的控制器 bean 将被创建,但是有些方法您期望一些 return 值,因此您必须在代码中编写预期的 return 值,这可能是这样做
Mockito.when(mapper.mapToEntity(Mockito.any(CustomerDto.class))).thenReturn(customer);
如果你错过了这个特定的步骤,那么在控制器中你将在这行代码中得到一个 NULL 指针异常
message = "Customer with " + customer.getId() + " sucessfully added";
因为你编码
Customer customer = mapper.mapToEntity(customerDto);
将 return NULL。
我希望这会帮助并激励您获得更多关于这些概念的知识。
如果需要任何进一步的帮助,请告诉我
尝试使用 @MockBean
或 @Mock
模拟 PropertyService。
我注意到您在 class 定义之上缺少 @WebMvcTest(Controller.class)
,您需要对 Mvc 控制器进行单元测试。
Explanation
如果@MockBean
不起作用。
尝试:
使用 Mockito.when()
,您可以简单地 return 您的 desired/expected 结果以及何时调用所需的方法。
使用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/resources 中唯一的 属性 文件。将两个资源文件夹中的文件重命名为完全相同的文件,看看会发生什么。
还有一个错误。这是一个主要问题——即使我提供了错误的 authId ,我的测试用例也通过了。请在您当地尝试,如果您有相同的行为,请告诉我。
当然你的测试将通过,因为你的代码只是检查条件它不会阻止代码被执行,请参考下面的代码:
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条件之外的代码块即可。