问题
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:
- 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.
- 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:
- 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. - 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