I have just finished reading Testing Rails guide by thoughtbot. The author mentions that using let!
or let
is an antipattern because in your test you dont get to see the variables that are needed for the setup.
I have this one common case that I'm curious how to deal with however if lets would be removed. I'm creating tests for creating a cart. When a cart is created, many things are tested. The current test is as follows (I removed the implementation of some methods and just kept the structure for brevity):
RSpec.describe Mutations::Carts::CreateCart, type: :request do
context 'Sending a request with an unauthorized user' do
it 'Creates a new cart and set the table for it' do
# Test setup
store = create :store
branch = store.branches.first
table = create :table, branch: branch
create_dependencies_for_create_cart(store)
cart_count_before_request = Cart.count
# Exercise
send_create_cart(store.id, table.id)
# Verify: (Some expects)
end
it 'Returns the branch id and and the table id' do
# Test setup
store = create :store
branch = store.branches.first
table = create :table, branch: branch
create_dependencies_for_create_cart(store)
# Exercise
send_create_cart(store.id, table.id)
# Verify: Some expects
end
end
private
def send_create_cart(store_id, table_id)
# Prepare and send the request
end
def create_dependencies_for_create_cart(store)
# Creating some objects that need to be created before the cart is created
end
end
My problem here is that the test setup is common and needs to only run once. Also the request can be sent once.
One tiny improvement over this would be to add a before(:each)
. This would on the one hand DRY the code a bit, but on the other hand, I'm still running the setup exercise twice potentially slowing my tests down, and I'm making the test less readable. before(:all)
would at least ensure that the setup and exercise are only run once, still this is deperacted and recommended against by the Ruby community.
What's the clean and effecient way (both readable and requires no repeated setups) to test such cases?
CodePudding user response:
All create
should be set via let
. Keep in mind that all your specs must be independent all the time. No matter the order they have been run.
If you've a test that checks if a new cart has be created. That's one test. Another one might be to check the response.
Even if best practices say it's better to have a single expect
per spec, sometime it makes sense to have several. It's all about context and logical for other dev to debug your code later on.
Here is my proposition:
context 'Sending a request with an unauthorized user' do
subject { send_create_cart(store.id, table.id) }
let(:store) { create(:store) }
let(:branch) { create(:branch, store: store)} # check according to your association
let!(:table) { create(:table, branch: branch) }
# Create dependencies for cart here, on `let` too.
# use `!` to force creation
# Here only `table` has `!` since other variable are called to create this one.
it 'Creates a new cart and set the table for it' do
expect{ subject }.to change { Cart.count }.by(1)
end
it 'Returns the branch id and and the table id' do
expect(subject).to eq([branch.id, table.id])
end
private
def send_create_cart(store_id, table_id)
# Prepare and send the request
end
end
CodePudding user response:
A good approach would be using a describe with a context with a different describes inside it each having its own before do.
describe 'Sending a request' do
context ' with an unauthorized user' do
store = create :restaurant
branch = store.branches.first
table = create :table, branch: branch
describe 'creating cart and table' do
before do
create_dependencies(store)
cart_count_before_request = Cart.count
# Exercise
send_create_cart()
end
it 'should not create a cart' do
# Verify: (Some expects)
end
it 'returns the ids'do
# Verify: Some expects
end
end
end
end