java.lang.AssertionError: Status Expected :200 Actual :404

在练习 Springboot 2.x 搭建项目做 mock 测试时出现 404 错误,网上资料少且现有解决方法未解决。作者发现是因 @RequestMapping 和测试代码中地址未写“/”导致,建议统一写法以避免该问题。

练习Springboot2.x搭建项目的时候做mock测试报出如下错误:

java.lang.AssertionError: Status 
Expected :200
Actual   :404

网上资料不多,查找了几个解决方法并没有解决问题
简书上有一个是这样写的:
在这里插入图片描述
参考:简书:糖先生要健康生活的文章:spring boot mock mvc 404错误原因
我的错误是另外一个原因:
Controller代码:

@RestController
public class UserController {
    @RequestMapping("hello")
    public String hello(){
        return "hello world2";
    }
}

Test代码:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

/**
 * @Author: lt
 * @Date: 2019/7/10 15:31
 */
@WebAppConfiguration
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest {
  @Autowired
  private WebApplicationContext wac;


  private MockMvc mockMvc;

  @Before
  public void setup() {
    mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
  }

  @Test
  public void hello() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("hello").accept(MediaType.APPLICATION_JSON))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andDo(MockMvcResultHandlers.print())
            .andReturn();
  }
}

因为习惯问题,@RequestMapping(“hello”)的地址会不写“/”,在测试 mockMvc.perform(MockMvcRequestBuilders.get(“hello”).accept(MediaType.APPLICATION_JSON))也没有写“/”
就是这个“/”导致了404

正确写法:

mockMvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON))

为了统一,建议@RequestMapping(“hello”)也写成:

@RequestMapping("/hello")
void testBuyOrderProcess(JobParameters parameters) throws Exception { mockCurrencyEntity("USD", "JP_GROUP1"); mockBusinessDateEntity(11); mockFixedRateList(2); when(exchangeOrderManageRepository.multiGetBatchTargets(any())).thenReturn(createBuyOrderTargets()); when(exchangeBusinessDateRepository.update(any())).thenReturn(1); when(depositWithdrawalLogic.doRepaymentForEc(any(), any())).thenReturn(true); when(depositWithdrawalLogic.doReceipt(any(), any(), anyInt(), anyString(), any())).thenReturn(true); var execution = jobLauncherTestUtils.launchStep(STEP_NAME, parameters); assertThat(execution.getExitStatus()).isEqualTo(ExitStatus.COMPLETED); ArgumentCaptor<MultiGetBatchTargetEntity> multiGetBatchTargetEntityArgumentCaptor = ArgumentCaptor.forClass( MultiGetBatchTargetEntity.class); ArgumentCaptor<Account> accountArgumentCaptor = ArgumentCaptor.forClass(Account.class); verify(depositWithdrawalLogic).doRepaymentForEc(multiGetBatchTargetEntityArgumentCaptor.capture(), accountArgumentCaptor.capture()); assertThat(multiGetBatchTargetEntityArgumentCaptor.getValue()).usingRecursiveComparison() .ignoringCollectionOrder().isEqualTo(createBuyOrderTargets().getFirst().toString()); assertThat(accountArgumentCaptor.getValue()).usingRecursiveComparison() .ignoringCollectionOrder().isEqualTo( createAccount(createBatchTargets().getFirst())); verify(depositWithdrawalLogic).doReceipt(any(), any(), any(), any(), any()); } 报出一下错误 Expecting actual: com.sbibits.evanes.exchange.batch.entity.MultiGetBatchTargetEntity@4d73d3d1 to be equal to: "com.sbibits.evanes.exchange.batch.entity.MultiGetBatchTargetEntity@28d728f1" when recursively comparing field by field, but found the following difference: Top level actual and expected objects differ: - actual value : com.sbibits.evanes.exchange.batch.entity.MultiGetBatchTargetEntity@4d73d3d1 - expected value: "com.sbibits.evanes.exchange.batch.entity.MultiGetBatchTargetEntity@28d728f1" Compared objects have java types and were thus compared with equals method The recursive comparison was performed with this configuration: - no equals methods were used in the comparison EXCEPT for java JDK types since introspecting JDK types is forbidden in java 17+ (use withEqualsForType to register a specific way to compare a JDK type if you need it) - collection order was ignored in all fields in the comparison - these types were compared with the following comparators: - java.lang.Double -> DoubleComparator[precision=1.0E-15] - java.lang.Float -> FloatComparator[precision=1.0E-6] - java.nio.file.Path -> lexicographic comparator (Path natural order) - actual and expected objects and their fields were compared field by field recursively even if they were not of the same type, this allows for example to compare a Person to a PersonDto (call strictTypeChecking(true) to change that behavior). - the introspection strategy used was: DefaultRecursiveComparisonIntrospectionStrategy java.lang.AssertionError: Expecting actual: com.sbibits.evanes.exchange.batch.entity.MultiGetBatchTargetEntity@4d73d3d1 to be equal to: "com.sbibits.evanes.exchange.batch.entity.MultiGetBatchTargetEntity@28d728f1" when recursively comparing field by field, but found the following difference: Top level actual and expected objects differ: - actual value : com.sbibits.evanes.exchange.batch.entity.MultiGetBatchTargetEntity@4d73d3d1 - expected value: "com.sbibits.evanes.exchange.batch.entity.MultiGetBatchTargetEntity@28d728f1" Compared objects have java types and were thus compared with equals method The recursive comparison was performed with this configuration: - no equals methods were used in the comparison EXCEPT for java JDK types since introspecting JDK types is forbidden in java 17+ (use withEqualsForType to register a specific way to compare a JDK type if you need it) - collection order was ignored in all fields in the comparison - these types were compared with the following comparators: - java.lang.Double -> DoubleComparator[precision=1.0E-15] - java.lang.Float -> FloatComparator[precision=1.0E-6] - java.nio.file.Path -> lexicographic comparator (Path natural order) - actual and expected objects and their fields were compared field by field recursively even if they were not of the same type, this allows for example to compare a Person to a PersonDto (call strictTypeChecking(true) to change that behavior). - the introspection strategy used was: DefaultRecursiveComparisonIntrospectionStrategy at com.sbibits.evanes.exchange.batch.job.b03003.B03003BatchTest.testBuyOrderProcess(B03003BatchTest.java:221) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at java.base/java.util.Optional.ifPresent(Optional.java:178) at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) 怎么修改
最新发布
11-04
<think>好的,用户遇到了一个单元测试中的问题,测试预期结果是"Hello bar",但实际返回了null。我需要仔细分析可能的原因,并提供解决方案。 首先,用户提供的测试代码中,title方法返回null,说明在某个环节没有正确设置模拟或调用真实方法。根据之前的对话历史,用户已经尝试了模拟notificationRuleService的getTitle方法,但可能没有正确触发。 可能的原因有几个: 1. **notificationTemplateSource设置问题**:用户提到在setUp中将notificationTemplateSource设置为true,但需要确认在测试执行时这个值是否确实为true。如果该字段被其他代码覆盖,可能导致进入不同的分支。 2. **titleFromMongo或titleFromProps未被正确调用**:根据之前的代码,title方法会根据notificationTemplateSource的值决定调用titleFromMongo还是titleFromProps。如果这两个方法内部没有正确使用模拟的getTitle或titleFromProps返回值,可能导致结果为null。 3. **mock对象未正确注入**:用户可能在helper初始化时没有正确注入mock的notificationRuleService,导致实际调用的是真实对象而非模拟对象。 4. **方法调用顺序或条件未满足**:在title方法中,可能存在某些条件判断导致未执行到获取title的步骤,例如multiLanguageProviderService为null,或者notificationConfig的获取失败。 接下来需要逐步验证这些可能性: 首先,检查notificationTemplateSource的值是否正确。在测试方法中添加断言,确认helper.isNotificationTemplateSource()返回true,确保进入正确的分支。 其次,确认titleFromMongo是否被正确调用。如果titleFromMongo方法内部调用了notificationRuleService.getTitle,需要确保该调用的参数与模拟的when条件一致。例如,参数是否使用了eq()匹配,或者是否存在拼写错误。 另外,检查helper是否正确注入了mock的notificationRuleService。在setUp中,用户通过构造器注入,但可能存在其他字段覆盖的情况。可以打印helper中的notificationRuleService是否为mock对象。 如果titleFromMongo返回null,可能是因为模拟的getTitle没有被正确触发,或者titleFromMongo本身有其他逻辑导致返回null。此时需要验证titleFromMongo的实现,或者在测试中模拟titleFromMongo的返回值,而不是getTitle。 例如,如果titleFromMongo方法直接调用notificationRuleService.getTitle,那么模拟notificationRuleService.getTitle是正确的。但如果titleFromMongo有其他逻辑,比如从数据库获取数据,可能需要模拟该方法本身。 最后,检查multiLanguageProviderService的模拟是否正确,是否在title方法中被正确调用,导致后续流程未执行到获取title的部分。 总结可能的解决方案: 1. 确保notificationTemplateSource为true。 2. 在titleFromMongo或titleFromProps中正确模拟依赖的方法。 3. 使用spy包装helper,直接模拟titleFromMongo的返回值。 4. 检查参数匹配,使用eq()确保参数正确匹配。 5. 验证mock对象是否正确注入。 根据这些思路,用户需要调整测试代码,确保所有依赖项正确模拟,并正确触发预期的方法调用。 </think>你的测试返回 `null` 的根本原因在于 `titleFromMongo` 方法未正确返回模拟值,导致最终结果为 `null`。以下是分步排查和解决方案: --- ### ✅ 根本原因定位 #### 1. **`titleFromMongo` 方法未模拟** 在 `title` 方法中,当 `notificationTemplateSource == true` 时,最终调用的是: ```java return titleFromMongo(msgType, locale, data); ``` 如果 `titleFromMongo` 内部逻辑是调用 `notificationRuleService.getTitle(...)`,那么你的 `when(notificationRuleService.getTitle(...)).thenReturn(...)` 是正确的。 但若 `titleFromMongo` **没有调用 `notificationRuleService.getTitle`**,而是直接访问数据库或其他数据源,则你的模拟不会生效,导致返回 `null`。 #### 2. **参数匹配失败** Mockito 的 `when(...)` 依赖精确的参数匹配。如果 `titleFromMongo` 中调用 `notificationRuleService.getTitle` 时传递的参数与 `when(...)` 中的参数不一致(如 `msgType` 大小写不同、`locale` 格式差异),模拟不会触发。 #### 3. **`notificationTemplateSource` 未正确设置** 虽然你在 `setUp` 中调用了 `helper.setNotificationTemplateSource(true)`,但需在测试方法中添加断言确认: ```java assertTrue(helper.isNotificationTemplateSource()); ``` --- ### ✅ 解决方案 #### 1. **直接模拟 `titleFromMongo` 的返回值** 如果 `titleFromMongo` 是 `protected` 或 `package-private`,可以使用 `Mockito.spy` 直接模拟其返回值,绕过 `notificationRuleService.getTitle` 的调用: ```java @Test public void title_withProvider_replacesVariablesAndCapitalizes() { // 创建 spy 对象 AlertFormatHelper spyHelper = Mockito.spy(helper); // 模拟 titleFromMongo 的返回值 doReturn("hello ${foo}").when(spyHelper).titleFromMongo(MSG_TYPE, LOCALE, raw); // 其余逻辑保持不变... // Act String result = spyHelper.title(MSG_TYPE, LOCALE, raw); // Assert assertEquals("Hello bar", result); } ``` #### 2. **确保 `notificationRuleService.getTitle` 被正确调用** 如果坚持使用 `notificationRuleService.getTitle` 模拟,需确保 `titleFromMongo` 中调用了它,并且参数完全匹配: ```java // 在测试中强制参数匹配 when(notificationRuleService.getTitle(eq(MSG_TYPE), eq(LOCALE), eq(APP_CATEGORY))) .thenReturn("hello ${foo}"); ``` #### 3. **验证 `notificationTemplateSource` 状态** 在测试方法中添加断言: ```java assertTrue(helper.isNotificationTemplateSource()); ``` #### 4. **检查 `multiLanguageProviderService.replaceDataWithTargetLanguage` 的模拟** 如果 `replaceDataWithTargetLanguage` 返回的 `data` 中没有 `foo` 字段,`titleFromMongo` 可能无法正确替换变量。确保模拟返回值包含所需字段: ```java JSONObject replacedData = new JSONObject(Collections.singletonMap("foo", "bar")); when(multiLanguageProviderService.replaceDataWithTargetLanguage( any(JSONObject.class), anyList(), eq(MSG_TYPE), eq(LOCALE))) .thenReturn(replacedData); ``` --- ### ✅ 修正后的完整测试示例 ```java @Test public void title_withProvider_replacesVariablesAndCapitalizes() { // Arrange JSONObject raw = new JSONObject(Collections.singletonMap("foo", "bar")); NotificationConfig cfg = new NotificationConfig(); cfg.setMultilingualVocabularys(Arrays.asList("foo")); // 模拟 notificationRuleService 的方法 when(notificationRuleService.getNotificationConfig(MSG_TYPE, LOCALE, APP_CATEGORY)) .thenReturn(cfg); // 模拟多语言替换 JSONObject replacedData = new JSONObject(Collections.singletonMap("foo", "bar")); when(multiLanguageProviderService.replaceDataWithTargetLanguage( raw, cfg.getMultilingualVocabularys(), MSG_TYPE, LOCALE)) .thenReturn(replacedData); // 直接模拟 titleFromMongo 的返回值 AlertFormatHelper spyHelper = Mockito.spy(helper); doReturn("hello ${foo}").when(spyHelper).titleFromMongo(MSG_TYPE, LOCALE, raw); // Act String result = spyHelper.title(MSG_TYPE, LOCALE, raw); // Assert assertEquals("Hello bar", result); // 验证调用顺序 InOrder inOrder = inOrder(notificationRuleService, multiLanguageProviderService); inOrder.verify(notificationRuleService).getNotificationConfig(MSG_TYPE, LOCALE, APP_CATEGORY); inOrder.verify(multiLanguageProviderService).replaceDataWithTargetLanguage(raw, cfg.getMultilingualVocabularys(), MSG_TYPE, LOCALE); } ``` --- ### ❗️关键点总结 1. **`titleFromMongo` 的模拟优先级高于 `notificationRuleService.getTitle`**:如果 `titleFromMongo` 是复杂方法,直接模拟它的返回值更可靠。 2. **参数匹配必须严格**:使用 `eq(...)` 确保参数完全一致。 3. **验证字段状态**:确认 `notificationTemplateSource` 和依赖项正确注入。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值