在控制器测试中维护使用 FactoryBot 创建的 ActiveRecord 关联

问题描述

我正在尝试加快 Rails 控制器的一些测试,瓶颈与创建大量对象并将其持久化到数据库有关。我正在尝试将大部分 create 调用替换为 build 调用解决此问题。

运行 Rails 5.1,并使用 MiniTest 5.10.3FactoryBot 5.0.2

我正在尝试从这里开始

@user = create(:user)
@item1 = create(:item)
@item1 = create(:item)

@transaction1 = create(:transaction,buyer: @buyer,item: @item1)
@transaction2 = create(:transaction,item: @item2)

在此应用程序中,item 表示可销售对象,user 表示购买者,而 transaction 是创建两者的对象。 User 类还添加一个关联 transaction_checkout_items,它返回处于可以完成购买状态的所有 Transaction 项。

因此,在每次测试中,我们都会创建无数对象并将它们全部保存到数据库中。它很慢,但它有效。不过,我希望它更快,所以我尝试用这样的东西替换现有的设置:

@user = create(:user)
def build_transaction_checkout_items(user,item)
  user.transaction_checkout_items.build(attributes_for(:transaction,buyer: user,sale_price: item.sale_price,item: item))
end

@item1 = build_stubbed(:item)
@item2 = build_stubbed(:item)
@transaction1 = build_transaction_checkout_items(@buyer,@item1)
@transaction2 = build_transaction_checkout_items(@buyer,@item2)

只要我在测试中,这似乎就可以工作。如果我在测试中删除绑定并检查对象 @user 返回用户对象,@user.transaction_checkout_items 返回包含我所有关联交易的 Transaction::ActiveRecord_Associations_CollectionProxy 对象,并且各个交易都附加了它们的关联项目.但是,如果我将 binding.pry 放入实际完成工作的控制器方法中,并查看 User 对象,我会看到正确的对象,但是 user.transaction_checkout_items 现在返回一个空的 {{1 }} 没有任何内容的对象。基本上关联消失了,这对我来说很有意义,因为控制器从数据库提取 Transaction::ActiveRecord_Associations_CollectionProxy 对象并开始处理它,而这个新对象缺少关联。我曾考虑尝试在 User 类上存根 any_instance 方法,以便每当调用 User 时它都会返回 #transaction_checkout_items 对象的集合,但我没有看到以任何方式创建新的 Transaction 对象。我不能简单地为此使用数组或其他集合,因为 ::ActiveRecord_Associations_CollectionProxy 上有一些方法需要调用才能使控制器逻辑工作。

所以我在星期五被封锁了。我在任何 ::ActiveRecord_Associations_CollectionProxy 实例上存根 transaction_checkout_items 的想法是否是一个好主意,如果是这样,我该怎么做?或者是否有任何人可以建议的替代策略,允许 MiniTest 存根关联在控制器代码运行时持续存在并可用?

解决方法

我在任何 User 实例上存根 transaction_checkout_items 的想法是否可行,如果是,我该怎么做?

剔除 ActiveRecord 方法几乎总是一个坏主意。它会将您的测试与实现紧密耦合,并且可能会使更新 Rails / ActiveRecord 变得困难,就好像您的测试开始破坏框架中的任何更改一样。可能还有很多你没有想到的有趣的副作用。

问题也是你真正想测试什么?如果你开始排除这些方法,你在测试什么?在控制器/集成测试中,我希望您希望测试从数据库中获取正确的记录。

使用构建与创建来提高测试性能是一个很好的技巧,但正如您已经发现的那样,只有在测试中使用相同的对象时才有价值。不幸的是,这对于集成测试是不可能的,您需要/应该保留记录。

或者是否有任何人可以建议的替代策略,允许 MiniTest 存根关联在控制器代码运行时持续存在并可用?

我会考虑为什么这很慢,以及这是否真的是一个问题。您的测试和整个测试套件运行了多长时间,或者这只是过早的优化?

如果它缓慢的原因仅仅是因为您需要创建大量测试数据,您可以使用夹具或为数据库设置种子。尽管会带来不同的问题(例如 MysteryGuest

,但这都比使用 FactoryBot 更快