Do you have problems deciding what to test or how to test you rails app? Don’t worry, this is a very common problem… I think that there are a lot of reasons why this is hard for many people, but I have found that one of the problems is that are many ways to do it.
So here, I will try to compare three different ways to test the same feature, so you can decide which one is better for you.
Imagine that you are building a catalog that:
So, lets start with the first way…
Here is one way to write the tests for the specification…
feature "Users sees the catalog" do
scenario "with all products sorted by name" do
create :product, name: "product A"
create :product, name: "product B"
visit catalog_path
expect_ordered_products("product A", "product B")
end
scenario "with the name, description and price of each product" do
create :product, name: "product 1", description: "p1 - desc", price: 1000
visit catalog_path
expect(page).to have_content "product 1"
expect(page).to have_content "p1-desc"
expect(page).to have_content "$1,000.00"
end
scenario "with a product without price" do
create :product, price: nil
visit catalog_path
expect(page).to have_content "Call for price"
end
scenario "with featured products" do
create :product, name: "product A"
create :product, name: "product B"
create :product, name: "product Featured", feature: true
visit catalog_path
expect_ordered_products("product Feature", "product A", "product B")
end
end
Look how in each scenario I try to describe the requirements of the
specification and also that I try to avoid as much as I can the
implementation details, for example I am using a create
method that
will create products for me, and also I am expecting to have a method
expect_ordered_products
instead of trying to jump in to the
implementation details while I am still thinking in the behavior.
Pros:
Cons:
This is a little harder because we are going to test the expected behavior in different places.
In the controller we can test…
RSpec.describe ProductsController do
describe "GET index" do
describe "assigns @products" do
it "with the products sorted by name" do
create :product, name: "B"
create :product, name: "A"
get :index
expect(assigns(:products).map(&:name)).to eq(["A", "B"])
end
it "with the featured products first" do
create :product, name: "B"
create :product, name: "A"
create :product, name: "Featured"
get :index
expect(assigns(:products).map(&:name)).to eq(["Featured", "A", "B"])
end
end
end
end
In the view we can test…
RSpec.describe "products/index" do
it "shows the name, description and price of the products" do
assign(:products, [create :product, name: "Product-A", description: "A-desc", price: 1000])
render
expect(rendered).to match /Product-A/
expect(rendered).to match /A-desc/
expect(rendered).to match /$1,000.00/
end
it "shows 'call for price' for products without price" do
assign(:products, [create :product, name: "A"])
render
expect(rendered).to match /Call for price/
end
end
Here look also how in each test description I am trying to express the requirements of the specification. That is why in this case I think that we don’t need model tests, because we have are already tested what our specification says.
Pros:
Cons:
Imagine that you will call a function in the controller and the thing that it returns is all that you need to render the content… something like this:
def index
@products = Catalog.get_products(Product)
end
# products/index.haml
- products.each do |product|
%td= product.name
%td= product.description
%td
- if product.has_price?
= number_to_currency product.price
- else
Call for price
I think that if we test the Catalog.get_products
method, we can test…
So translating this in to rspec, we have…
module Catalog
RSpec.describe "Get products" do
it "returns all the stored products sorted by name"
it "returns the featured products first"
describe "each product" do
it "with a name, description and price"
it "knows if has price or not"
end
end
end
Now lets continue with the implementation of our tests…
module Catalog
RSpec.describe "Get products" do
def get_products(store)
Catalog.get_products(store)
end
it "returns all the stored products sorted by name" do
products = get_products(store_with([
product_with(name: "B"),
product_with(name: "A")
]))
expect(products.map(&:name)).to eq ["A", "B"]
end
it "returns the featured products first" do
products = get_products(store_with([
product_with(name: "B"),
product_with(name: "A"),
product_with(name: "Featured")
]))
expect(products.map(&:name)).to eq ["Featured", "A", "B"]
end
describe "each product" do
it "with a name, description and price" do
product = get_products(store_with([
product_with(name: "A", descrition: "A-desc", price: 1000)
])).first
expect(product.name).to eq "A"
expect(product.description).to eq "A-desc"
expect(product.price).to eq 1000
end
it "knows if has price or not" do
with_price, without_price = get_products(store_with([
product_with(name: "A", price: 1000),
product_with(name: "B", price: nil)
]))
expect(with_price).to have_price
expect(without_price).not_to have_price
end
end
end
end
Look how we are not testing exactly for what our specification says, but we are testing for something that will help us implement the specification in a very easy way. I really prefer this approach over the other two, but you may like different things.
Some pros and cons…
Pros:
Cons:
Now is up to you… you have a little more knowledge to decide your way… There is no “true way” or at least I don’t know one. I just have one method that works best for me.
I send an email each week, trying to share knowledge and fixes to common problems and struggles for ruby on rails developers, like How to fetch the latest-N-of-each record or How to test that an specific mail was sent or a Capybara cheatsheet. You can see more examples on Most recent posts or All post by topic.