Просмотр исходного кода

✅ test(cart): 修复库存不足提示测试的API查询mock配置

修复购物车页面测试用例"应该显示库存不足提示(API查询成功)":
- 修复jest.mock模块替换配置,解决require('@/api').goodsClient返回undefined问题
- 使用jest.spyOn确保API查询mock正确生效
- 在每个测试用例中独立设置mock实现,避免测试间相互干扰
- 更新故事006.008文档记录修复过程

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
yourname 1 месяц назад
Родитель
Сommit
45c24a99c0

+ 21 - 17
docs/stories/006.008.cart-spec-switching.story.md

@@ -137,6 +137,7 @@ Completed
 | 2025-12-13 | 1.0 | 初始故事创建 | Bob (Scrum Master) |
 | 2025-12-13 | 1.1 | 故事实施完成,测试验证通过 | Claude Code |
 | 2025-12-13 | 1.2 | 测试重构修复:修复购物车页面测试,使用真实CartContext,解决空购物车状态测试问题,16个测试通过 | James (Developer) |
+| 2025-12-13 | 1.3 | 库存提示测试修复:修复"应该显示库存不足提示(API查询成功)"测试用例,解决jest.mock配置和API查询mock问题,18个测试全部通过 | James (Developer) |
 
 ## Dev Agent Record
 *此部分由开发代理在实施过程中填写*
@@ -156,13 +157,14 @@ Claude Sonnet 4.5 (claude-sonnet-4-5-20250929)
 6. 任务6完成:通过代码审查验证父子商品租户约束,switchSpec操作性能良好,本地存储更新效率合理
 7. 测试修复完成:修复购物车页面测试的Taro mock配置,使用统一的taroMock文件,确保所有核心测试通过
 8. 测试重构完成:修复购物车页面测试,使用真实CartContext替代mock,重构测试结构,解决空购物车状态测试失败问题,16个测试通过,1个跳过
+9. 库存提示测试修复完成:修复"应该显示库存不足提示(API查询成功)"测试用例,解决jest.mock模块替换配置问题,使用jest.spyOn确保API查询mock正确生效,18个购物车页面测试全部通过
 
 ### File List
 **创建/修改的文件:**
 1. `mini/src/contexts/CartContext.tsx` - 扩展CartContext,添加switchSpec函数,更新CartItem接口
 2. `mini/src/pages/cart/index.tsx` - 集成GoodsSpecSelector组件,添加规格切换功能,添加调试日志跟踪库存提示状态
 3. `mini/tests/unit/contexts/CartContext.test.tsx` - 添加switchSpec函数单元测试
-4. `mini/tests/unit/pages/cart/index.test.tsx` - 添加购物车页面规格切换组件测试,修复测试结构使用真实CartContext,解决空购物车状态测试问题,修复库存不足提示测试(调整item.stock为2,更新期望文本为"仅剩2件")
+4. `mini/tests/unit/pages/cart/index.test.tsx` - 添加购物车页面规格切换组件测试,修复测试结构使用真实CartContext,解决空购物车状态测试问题,修复库存不足提示测试(调整item.stock为2,更新期望文本为"仅剩2件"),修复"应该显示库存不足提示(API查询成功)"测试用例,解决jest.mock配置和API查询mock问题
 5. `mini/tests/__mocks__/taroMock.ts` - 扩展Taro API mock,添加request方法支持
 
 **影响但未修改的文件:**
@@ -170,7 +172,7 @@ Claude Sonnet 4.5 (claude-sonnet-4-5-20250929)
 2. `packages/goods-module-mt/src/entities/goods.entity.mt.ts` - 商品实体定义(参考父子商品关系)
 
 ### Remaining Test Issues
-*以下测试在本次实现中被跳过,建议后续修复:*
+*以下测试在本次实现中被识别并修复:*
 
 1. **空购物车状态测试**(2个测试):**已修复 ✅**
    - `应该显示空购物车状态` - 已通过重构测试结构解决,使用真实CartContext和Taro存储mock
@@ -178,24 +180,26 @@ Claude Sonnet 4.5 (claude-sonnet-4-5-20250929)
 
    **修复方法**:重构测试结构,移除jest.mock,使用真实CartContext配合Taro存储mock控制初始状态
 
-2. **库存不足提示测试**(1个测试):**已修复** ✅
+2. **库存不足提示测试**(2个测试):**已修复** ✅
    - `应该显示库存不足提示` - 测试已通过,正确显示库存提示文本"仅剩2件"
-
-   **根本原因分析正确**:购物车页面组件中,库存提示确实依赖从API查询获取的最新商品信息,但同时也支持使用本地购物车项的stock值作为后备
+   - `应该显示库存不足提示(API查询成功)` - 测试已通过,正确显示库存提示文本"仅剩1件"
 
    **修复过程**:
-   - 通过添加调试日志确认数据流:商品查询启动但未返回数据(测试环境异步查询问题)
-   - 商品信息正确使用后备值:当`goodsMap`无数据时使用`item.stock`(值为2)
-   - 库存提示条件 `goodsStock <= 3` 触发正确:2 <= 3为true
-   - 测试期望修正:预期文本改为"仅剩2件"(匹配item.stock值而非mock API返回值)
-
-   **修复确认**:
-   - 调试日志显示:`商品信息: 2 名称: 测试商品2 库存: 2 goodsMap中有: false latestGoods: 无 item.stock: 2`
-   - 库存提示检查:`库存提示检查: 2 goodsStock: 2 <=3? true`
-   - 最终渲染:`Stock mask text: 仅剩2件`
-   - 测试完全通过,无需mock useQueries,使用真实组件逻辑
-
-**修复完成**:所有剩余测试问题已解决,库存提示测试现在完全通过,确保完整的测试覆盖。
+   - **测试1(`应该显示库存不足提示`)**:通过添加调试日志确认数据流,修复测试期望文本为"仅剩2件"(匹配item.stock值)
+   - **测试2(`应该显示库存不足提示(API查询成功)`)**:
+     - **问题分析**:jest.mock模块替换配置问题导致`require('@/api').goodsClient`返回`undefined`,API查询失败
+     - **根本原因**:jest.mock工厂函数作用域问题,`mockGoodsClient`变量在模块替换时不可访问
+     - **修复方法**:
+       1. 修复`jest.mock('@/api', ...)`配置,确保工厂函数正确处理变量作用域
+       2. 使用`jest.spyOn`直接监视`api.goodsClient[':id'].$get`方法,确保mock正确生效
+       3. 在每个测试用例中独立设置mock实现,避免测试间相互干扰
+     - **修复确认**:
+       - 调试日志显示商品查询成功返回库存为1:`商品查询成功: 2 测试商品2 库存: 1`
+       - `goodsMap`正确构建包含API返回数据:`添加到goodsMap: 2 测试商品2 库存: 1`
+       - 库存提示正确显示:`仅剩1件`
+       - 测试完全通过,18个购物车页面测试全部通过
+
+**修复完成**:所有测试问题已解决,18个购物车页面测试全部通过,确保完整的测试覆盖。
 
 ## QA Results
 *此部分由QA代理在审查完成后填写*

+ 0 - 1
mini/src/pages/cart/index.tsx

@@ -322,7 +322,6 @@ export default function CartPage() {
                               </View>
                             }
                           />
-                          {console.debug('库存提示检查:', item.id, 'goodsStock:', goodsStock, '<=3?', goodsStock <= 3)}
                           {goodsStock <= 3 && (
                             <View className="stock-mask">
                               仅剩{goodsStock}件

+ 99 - 3
mini/tests/unit/pages/cart/index.test.tsx

@@ -67,9 +67,15 @@ const mockGoodsClient = {
   }
 }
 
-jest.mock('@/api', () => ({
-  goodsClient: mockGoodsClient
-}))
+jest.mock('@/api', () => {
+  // 如果mockGoodsClient已经定义,使用它;否则创建默认mock
+  const goodsClientMock = typeof mockGoodsClient !== 'undefined' ? mockGoodsClient : {
+    ':id': {
+      $get: jest.fn()
+    }
+  }
+  return { goodsClient: goodsClientMock }
+})
 
 // Mock布局组件
 jest.mock('@/layouts/tab-bar-layout', () => ({
@@ -249,6 +255,96 @@ describe('购物车页面', () => {
     expect(await findByText('仅剩2件')).toBeDefined()
   })
 
+  it('应该显示库存不足提示(API查询成功)', async () => {
+    // 获取mock的goodsClient
+    const api = require('@/api')
+
+    // 使用spyOn确保我们监视正确的方法
+    const goodsClientSpy = jest.spyOn(api.goodsClient[':id'], '$get')
+    goodsClientSpy.mockReset()
+
+    // 设置购物车数据,商品2的本地库存为5(不触发提示),API返回1(触发提示)
+    const testCartItems = [
+      {
+        id: 1,
+        parentGoodsId: 100,
+        name: '测试商品1',
+        price: 29.9,
+        image: 'test-image1.jpg',
+        stock: 10,
+        quantity: 2,
+        spec: '红色/M',
+      },
+      {
+        id: 2,
+        parentGoodsId: 200,
+        name: '测试商品2',
+        price: 49.9,
+        image: 'test-image2.jpg',
+        stock: 5,  // 本地库存5,不触发提示(>3)
+        quantity: 1,
+        spec: '蓝色/L',
+      },
+    ]
+    mockGetStorageSync.mockReturnValue({ items: testCartItems })
+
+    // 设置mock返回正确的数据
+    goodsClientSpy.mockImplementation(({ param }: any) => {
+      const goodsId = param?.id
+
+      // 根据商品ID返回不同的库存数据
+      if (goodsId === 1) {
+        return Promise.resolve({
+          status: 200,
+          json: () => Promise.resolve({
+            id: 1,
+            name: '测试商品1',
+            price: 29.9,
+            imageFile: { fullUrl: 'test-image1.jpg' },
+            stock: 10
+          })
+        })
+      } else if (goodsId === 2) {
+        return Promise.resolve({
+          status: 200,
+          json: () => Promise.resolve({
+            id: 2,
+            name: '测试商品2',
+            price: 49.9,
+            imageFile: { fullUrl: 'test-image2.jpg' },
+            stock: 1  // 低库存,触发库存提示
+          })
+        })
+      }
+
+      // 默认返回商品1的数据
+      return Promise.resolve({
+        status: 200,
+        json: () => Promise.resolve({
+          id: 1,
+          name: '测试商品1',
+          price: 29.9,
+          imageFile: { fullUrl: 'test-image1.jpg' },
+          stock: 10
+        })
+      })
+    })
+
+    const { findByText } = renderWithProviders(<CartPage />)
+
+    // 等待商品2加载完成
+    await findByText('测试商品2')
+
+    // 等待查询完成
+    await new Promise(resolve => setTimeout(resolve, 200))
+
+    // 商品2的API库存是1,应该显示"仅剩1件"
+    expect(await findByText('仅剩1件')).toBeDefined()
+
+    // 清理spy
+    goodsClientSpy.mockRestore()
+  })
+
   it('应该显示广告区域', () => {
     const { container } = renderWithProviders(<CartPage />)
     const adElement = container.querySelector('.cart-advertisement')