setting up objects for model testing with factory_girl

我是研究僧i 提交于 2019-12-25 07:26:38

问题


I'd like to test the scopes below but don't know hot to setup test objects properly. I don't even know if I should create those in factory with some associations or using let/local vars.

task.rb

belongs_to :assigner, class_name: "User"
belongs_to :executor, class_name: "User"

scope :completed, -> { where.not(completed_at: nil) }
scope :uncompleted, -> { where(completed_at: nil) }
scope :alltasks, -> (u) { where('executor_id = ? OR assigner_id = ?', u.id, u.id) }
scope :between, -> (assigner_id, executor_id) do
  where("(tasks.assigner_id = ? AND tasks.executor_id = ?) OR (tasks.assigner_id = ? AND tasks.executor_id = ?)", assigner_id, executor_id, executor_id, assigner_id)
end

factories

factory :task do
  content { Faker::Lorem.sentence }
  deadline { Faker::Time.between(DateTime.now + 2, DateTime.now + 3) }
  association :executor, factory: :user
  association :assigner, factory: :user #I guess this is not right
end

factory :user do
  sequence(:email) { |n| "example#{n}@gmail.com" }
  password 'example0000'
  password_confirmation 'example0000'
  new_chat_notification { Faker::Number.between(0, 10) }
  new_other_notification { Faker::Number.between(0, 10) } 
end

task_spec.rb

describe Task do

  .....

  describe "scopes" do

    #I DON'T KNOW HOW TO SETUP THE TEST OBJECTS PROPERLY
    let(:uncompleted_task) { create(:task) }
    let(:completed_task) { create(:task, completed_at: DateTime.now - 2) }
    let(:user) { create(:user) 

    let(:task_1) { create(:task, executor_id: user.id, assigner_id: (user.id + 1)) }
    let(:task_2) { create(:task, assigner_id: user.id, executor_id: (user.id + 1) }
    let(:task_3) { create(:task, assigner_id: user.id, executor_id: (user.id + 2) }

    it "completed" do
      expect(completed_task).to eq(Task.completed.first)
    end

    it "uncompleted" do
      expect(uncompleted_task).to eq(Task.uncompleted.last)
    end

    it "alltasks" do

    end

    it "between" do
    end
  end
end

回答1:


First of all, I prefer NOT to setup associations in factories due to the following reasons:

  1. If there are associations in a factory, every time an object is created/built, the associated objects are created at the same time. It means many database accesses are triggered even you just 'build' an object. The more associations you use, the more time is required to run a test.
  2. You should setup associations in the test when you really need it. If you setup associations in a factory, it means the 'given' part of given-when-then process is hidden from the test. It becomes more difficult to find what is going wrong when a test is failed.

To test a scope, you should follow some best practices:

  1. Make your scopes as simple as possible, it is harder to test complicated scopes. For example, you should break down your between scope to small ones if needed.
  2. Use the minimal objects to test the scope because we need to create records in database to test a scope. The database access slows down the testing and we should reduce the object creation as possible. For most cases, two objects (positive and negative) should be enough to test a simple scope.

By the way, you may misunderstand the usage of let, the object defined with let is created only when the object is called in test cases. In your original specs, no tasks or users are created because none of them is called. To do what you want, you should use before block instead.

For your example, I would create factories and specs below:

Factories

factory :task do
  content { Faker::Lorem.sentence }
  deadline { Faker::Time.between(DateTime.now + 2, DateTime.now + 3) }
end

factory :user do
  sequence(:email) { |n| "example#{n}@gmail.com" }
  sequence(:password) { |n| "password#{n}" }
  password_confirmation { password }
  new_chat_notification { Faker::Number.between(0, 10) }
  new_other_notification { Faker::Number.between(0, 10) }
end

Specs

describe Task do
  # ...
  describe "scopes" do
    let(:executor) { create(:user) }
    let(:assigner) { create(:user) }

    describe "completeness related" do
      before(:example) do
        @uncompleted_task = create(:task, executor: executor, assigner: assigner)
        @completed_task = create(:task, executor: executor, assigner: assigner, completed_at: DateTime.now - 2))
      end

      it "completed" do
        expect(Task.completed).to include(@completed_task)
        expect(Task.completed).not_to include(@uncompleted_task)
      end

      it "uncompleted" do
        expect(Task.uncompleted).to include(@uncompleted_task)
        expect(Task.uncompleted).not_to include(@completed_task)
      end
    end

    it "alltasks" do
      me = create(:user)
      owned_task = create(:task, executor: me, assigner: assigner)
      assigned_task = create(:task, executor: executor, assigner: me)
      not_my_task = create(:task, executor: executor, assigner: assigner)
      expect(Task.alltasks(me)).to include(owned_task, assigned_task)
      expect(Task.alltasks(me)).not_to include(not_my_task)
    end

    it "between" do
      # ... (there should be as last 5 tasks to create)...
    end
  end
  # ...
end

There are more details and hits about how to write tests in the book "Rails 4 Test Prescriptions - Build a Healthy Codebase by Noel Rappin", and it is worth reading if you are interested.



来源:https://stackoverflow.com/questions/36610591/setting-up-objects-for-model-testing-with-factory-girl

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!