You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Testing Library测试Context时expect报innerHTML为null错误

问题修复方案

报错原因

  • 测试用例共享渲染实例:你把render方法放在了describe块的顶层,所有测试用例共用同一个组件渲染实例,容易出现状态污染,也不符合单测隔离的原则。
  • 未等待异步更新:React的状态更新是异步执行的,你点击添加按钮触发Context状态更新后,同步执行查询时组件还没完成重渲染,目标元素还不存在,queryByTitle会返回null,直接访问innerHTML属性就会抛出类型错误。
  • 无空值判断:queryBy*系列查询的特性就是找不到元素时返回null,直接访问属性前没有做空值校验也会触发报错。

如果排查完以上问题还是报错,可以确认CartContextProvider的addProduct逻辑是否正确,是否将传入的商品名赋值给了商品对象的itemId字段,避免title属性不匹配导致查询失败。另外原代码中列表渲染没有加key属性,也建议补充避免控制台警告。

修复方法

  1. 将渲染逻辑移动到每个测试用例内部,或者用beforeEach包裹,保证每个测试用例的运行环境独立。
  2. 用异步查询方法findBy*替代同步查询,等待元素渲染完成后再执行断言,也可以用waitFor包裹断言逻辑。
  3. 推荐用React Testing Library官方推荐的screen对象获取查询方法,减少变量解构带来的错误。

修正后测试代码示例

class CartContextTest extends React.Component {
  constructor(props) {
    super(props);
    this.state={
      productName: '',
      priceInUSD: 0,
      priceInEUR: 0,
      attribute: ''
    }
  }
  render() {
    return (
      <CartContextProvider>
        <CartContext.Consumer>
          {context=>{
            const {productsToPurchase, totalPrice, addProduct, decreaseProduct} = context
            return(<>
              <input type='text' title='product-name'
                     onChange={ (e)=>{this.setState({productName: e.target.value})} }
              />
              <input type='text' title='price-in-usd'
                     onChange={ (e)=>{this.setState({priceInUSD: e.target.value})} }
              />
              <input type='text' title='price-in-eur'
                     onChange={ (e)=>{this.setState({priceInEUR: e.target.value})} }
              />
              <input type='text' title='attribute'
                     onChange={ (e)=>{this.setState({attribute: e.target.value})} }
              />
              <button title='add-product' onClick={()=>{
                addProduct(this.state.productName,[{currency: 'USD', amount: this.state.priceInUSD},{currency: 'EUR', amount: this.state.priceInEUR}] , this.state.attribute)
              }}>Add</button>
              {productsToPurchase.map(product=>{
                return(<div key={product.itemId}>
                  <h1 title={"Name " + product.itemId}>{product.itemId}</h1>
                  <button title={"Plus " + product.itemId} onClick={()=>{addProduct(product.itemId, product.price, product.attributes)}}>+</button>
                  <h1 title={"qty " + product.itemId}>{product.qty}</h1>
                  <button title={"Minus " + product.itemId} onClick={()=>{decreaseProduct(product.itemId, product.attributes)}}>-</button>
                  <>{product.price.map(price=>{
                    return(<h1 key={price.currency + " " + product.itemId} title={price.currency + " " + product.itemId}>{price.amount}</h1>)
                  })}</>
                  <h1>{product.attributes}</h1>
                </div>)
              })}
            </>)
          }}
        </CartContext.Consumer>
      </CartContextProvider>
    );
  }
}

describe("Testing the ability to add and remove products from the cart", ()=>{
  // 每个测试用例执行前重新渲染组件,保证状态独立
  beforeEach(() => {
    render(<CartContextTest/>)
  })

  it('All the starter components should be rendered', ()=> {
    expect(screen.queryByTitle("product-name")).toBeTruthy()
    expect(screen.queryByTitle("price-in-usd")).toBeTruthy()
    expect(screen.queryByTitle("price-in-eur")).toBeTruthy()
    expect(screen.queryByTitle("attribute")).toBeTruthy()
    expect(screen.queryByTitle("add-product")).toBeTruthy()
  })

  it('Should add and remove products successfully', async ()=>{
    // 添加iPhone XR
    fireEvent.change(screen.queryByTitle("product-name"), {target:{value: "iphone-xr"}})
    fireEvent.change(screen.queryByTitle("price-in-usd"), {target:{value: "600"}})
    fireEvent.change(screen.queryByTitle("price-in-eur"), {target:{value: "500"}})
    fireEvent.change(screen.queryByTitle("attribute"), {target:{value: "blue"}})
    fireEvent.click(screen.queryByTitle("add-product"))

    // 异步等待目标元素渲染完成
    const productTitle = await screen.findByTitle("Name iphone-xr")
    expect(productTitle.innerHTML).toBe("iphone-xr")
  })
})

内容的提问来源于stack exchange,提问作者asem shaat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 22:06:04