Rails Minitest assert_equal断言失败求助:预期与实际值不符
场景说明
Cart与Line_item是一对多关联(Line_item属于Cart),Product与Line_item也是一对多关联(Line_item属于Product)。当向line_items_path发送POST请求时,product_id会传入line_items_controller#create方法,该方法调用cart#add_product:
- 如果购物车中已存在相同
product_id的Line_item,就将该Line_item的quantity加1 - 如果不存在,则构建新的Line_item,后续由控制器统一保存
相关代码
line_items_controller.rb
def create product = Product.find(params[:product_id]) @line_item = @cart.add_product(product.id, product.price) respond_to do |format| if @line_item.save format.html { redirect_to @line_item.cart } format.json { render :show, status: :created, location: @line_item } else format.html { render :new } format.json { render json: @line_item.errors, status: :unprocessable_entity } end end end
cart.rb 的 #add_product 方法
def add_product(product_id, product_price) current_item = line_items.find_by(product_id: product_id) if current_item current_item.quantity += 1 else current_item = line_items.build(product_id: product_id, price: product_price) end current_item end
测试用例与报错
测试用例
test "should update quantity of existing line item when adding another of the same product" do cart = Cart.create cart.add_product(products(:product_one).id, products(:product_one).price) cart.add_product(products(:product_one).id, products(:product_one).price) assert_equal cart.line_items.size, 1 # assert_equal cart.line_items[0].quantity, 2 end
报错信息
Failure: CartsControllerTest#test_should_update_quantity_of_existing_line_item_when_adding_another_of_the_same_product [AgileWebDev/depot/test/controllers/carts_controller_test.rb:73]: Expected: 2 Actual: 1
问题分析与解决方案
困惑点拆解
你提到的两个核心问题,我们逐一梳理解决:
断言参数顺序导致的数值矛盾
Rails的assert_equal方法参数顺序是assert_equal 预期值, 实际值。看你的测试代码,当前写的assert_equal cart.line_items.size, 1把「实际值」放在了第一个参数位置,「预期值」放在了第二个。如果cart.line_items.size是1,这条断言逻辑上应该通过,但报错显示Expected:2, Actual:1,说明你实际运行的断言应该是assert_equal 2, cart.line_items.size(或者你把注释里的断言写错了位置)。你注释里的
assert_equal cart.line_items[0].quantity, 2才是符合测试意图的,但同样要修正参数顺序,正确写法是:assert_equal 2, cart.line_items[0].quantity测试中未持久化变更导致的环境差异
开发环境功能正常,是因为line_items_controller#create里调用了@line_item.save——不管是新建Line_item还是更新已有Line_item的quantity,这个save都会把变更写入数据库。但你的测试代码里,直接调用cart.add_product后没有保存返回的Line_item:- 第一次调用
add_product时,build只是在内存中生成Line_item对象,不会写入数据库 - 第二次调用
add_product时,line_items.find_by(product_id: product_id)会去数据库查询,此时数据库里还没有这条记录,所以会再次build新的Line_item;即使是更新已有Line_item的quantity,current_item.quantity +=1也只是内存中的变更,不save的话数据库数值不会更新。
修正测试代码的两种方式:
- 方式一:在测试中手动保存Line_item,确保变更持久化
test "should update quantity of existing line item when adding another of the same product" do cart = Cart.create # 第一次添加并保存 line_item = cart.add_product(products(:product_one).id, products(:product_one).price) line_item.save # 第二次添加并保存 line_item = cart.add_product(products(:product_one).id, products(:product_one).price) line_item.save # 重新加载数据,确保获取数据库最新状态 cart.reload # 断言购物车中只有1条Line_item,且quantity为2 assert_equal 1, cart.line_items.size assert_equal 2, cart.line_items[0].quantity end - 方式二:修改
cart#add_product方法,更新quantity后自动保存(更推荐保持控制器统一处理保存,这里仅作备选)def add_product(product_id, product_price) current_item = line_items.find_by(product_id: product_id) if current_item current_item.quantity += 1 current_item.save # 新增保存逻辑 else current_item = line_items.build(product_id: product_id, price: product_price) end current_item end
- 第一次调用
总结
核心问题是测试代码的断言参数顺序错误,加上未持久化内存中的变更,导致测试与开发环境行为不一致。调整断言顺序并确保Line_item的变更保存到数据库后,测试就能正常通过了。
内容的提问来源于stack exchange,提问作者jbk

