使用 rspec 测试依赖模型

Testing dependent models with rspec

我对 ruby 和 rspec 比较陌生,想知道如何解决以下情况或者处理类似情况的首选方法是什么。为了简单起见,假设我有以下模型:

class Products
  has_one :image
  has_one :category

  def create_document
    {
      title: title,
      image: image.url unless image.nil?,
      category: category.name unless category.nil?,
    }
  end
end

class Images
  belongs_to :product
end

class Categories
  belongs_to :product
end

我要测试四种情况:

describe Product do
  # create all four products
  fixtures :products, :images, :categories

  it 'returns title' do
    # create only product
  end

  it 'returns title and category' do
    # create category
  end

  it 'returns title, image and category' do
    # create image
  end

  it 'returns title and image' do
    # remove category
  end
end

我的问题是所有固定装置都在我的测试开始执行之前创建。这意味着我可以创建 4 种产品(我知道如何解决)并分别测试它们中的每一种,或者创建一种产品并在我进行测试时创建图像和类别(这对我来说仍然是一个难题而且我不不知道如何解决)。处理这样的测试最好的方法是什么?

这是我的spec_helper.rb

ENV['RAILS_ENV'] = 'test'

require_relative '../config/environment.rb'

require 'rspec/rails'
require 'rack/utils'

ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)

RSpec.configure do |config|
  config.infer_base_class_for_anonymous_controllers = false
  config.order = "random"

  config.expect_with :rspec do |c|
    c.syntax = [:should, :expect]
  end

  config.before(:each) do
    Rails.cache.clear rescue nil
    Time.zone = 'UTC'
  end

  # define the factories
  require 'factory_girl'

  # configure fixture options
  config.fixture_path = "#{Rails.root}/spec/fixtures/"
  config.use_transactional_fixtures = true

  # Build the fixtures
  require_relative 'support/fixture_builder'
end

这是我的 fixture_builder.rb

require 'fixture_builder'

FixtureBuilder.configure do |fbuilder|
  fbuilder.files_to_check += Dir["spec/factories/**/*.rb", "spec/support/fixture_builder.rb"]

  fbuilder.factory do
    load(Rails.root.join('db/seeds.rb'))

    product_one = fbuilder.name(:product_one, FactoryGirl.create(:product, title: 'product 1')).first
    fbuilder.name(:image_one, FactoryGirl.create(:image, product_id: product_one.id, url: 'product 1 image url'))
    fbuilder.name(:category_one, FactoryGirl.create(:category, product_id: product_one.id, name: 'product 1 category'))
  end
end

还有我的工厂

FactoryGirl.define do
  factory :product, class: Product do
  end
end

FactoryGirl.define do
  factory :image, class: Image do
  end
end

FactoryGirl.define do
  factory :category, class: Category do
  end
end

FactoryGirl 用作固定装置的替代品。因此,您不使用 FactoryGirl 的 fixtures 方法,而是它有自己的 DSL:

FactoryGirl.create(:product) # Saves a product to the database
FactoryGirl.build_stubbed(:product) # Builds a product and makes it look look like it has been persisted
FactoryGirl.attributes_for(:product) 
# etc

您可以通过在规格中包含 FactoryGirl::Syntax::Methods 来设置快捷方式:

RSpec.configure do |config|
  # ..
  config.include FactoryGirl::Syntax::Methods
  # ..
end

然后您可以使用 rspecs letlet! 方法在您的规范中使用工厂。

describe "Let methods and factories" do

  let(:product) { create(:product) } # initialized when it is used
  let!(:foo) { create(:foo) } # initialized before any specs are run

  describe "let" do
    expect(product).to be_a Product
  end
end

database_cleaner gem 对于规范之间的清理非常宝贵,可以避免任何测试顺序问题或误报。

在您的情况下,您可能希望直接为每个测试用例使用工厂。

describe Product do

  # It is a good practice to put examples in a describe block 
  # which tells which method we are testing
  describe "#create_document" do

    it 'returns title' do
      product = build_stubbed(:product, title: build_stubbed(:title))
      expect(product.create_document()[:title]).to be_a Title
    end

    it 'returns title and category' do
      product = build_stubbed(:product, category: build_stubbed(:category))
      expect(product.create_document()[:category]).to be_a Category
    end
    #.. 
  end
end