Are you struggling trying to define what to test on your unit tests? Maybe is easier for you to identify what to test on your feature tests or system tests because the user story or approval criteria, in a certain way “tells you” what to test or what is expected to happen.
But with your unit tests is different…
…because now you want to test that class or module that you need to accomplish the total feature. And maybe you don’t know at what level or detail you have to write those tests… Do you need to test the method or the class inside that module? How do you separate the scenarios?…is not that easy!!
But what if instead of trying to write test for your models, presenters, controllers, etc… you write tests for the features that you need to implement, but not as a system tests, but as unit tests!…
Maybe now you will have to define and design your “units”… but is not that hard, and I think that this is the point of “unit” testing (don’t you think?)…
Imagine you are working on a CRM and there you need to write a feature to register a company in the system. And to register that company you need the name and the logo of the company.
So to test that with a “feature” test or “system” test, you can create
a spec/features/create_company_spec.rb
file and there you can write
something like…
feature "Create company" do
scenario "with name and logo" do
visit new_company_path
fill_in :name, with: "C1"
attach_file :logo, "/fixtures/logo.png"
expect(page).to have_content "Successfully created"
expect(page).to have_content "C1"
expect(page).to have_current_path "/companies/c1"
end
scenario "without name" do
visit new_company_path
fill_in :name, with: ""
expect(page).to have_content "Name can't be blank"
end
end
We are testing that…
Ooo yes we can =)… You can create an spec file spec/crm/create_company_spec.rb
…
And write something like…
module CRM
RSpec.describe "Create company" do
def new_company_form
CRM.new_company_form
end
def create_company(params, store)
CRM.create_company(params, store)
end
attr_reader :store, :params, :logo
before do
@store = DummyStore
@params = { "name" => "D1", "logo" => @logo = Object.new }
end
it "has a form" do
form = new_company_form
expect(form.name).to eq nil
expect(form.logo).to eq nil
end
describe "creates a record adding the slug" do
example do
expect(store).to receive(:create).with(name: "D1", logo: logo, slug: "d1")
create_company(params, store)
end
example do
name = "uno Dos trEs"
slug = "uno-dos-tres"
expect(store).to receive(:create).with(name: name, logo: logo, slug: slug)
create_company(params.merge("name" => name), store)
end
end
it "returns success" do
status = create_company(params, store)
expect(status).to be_success
end
describe "without name" do
it "does not returns success" do
status = create_company({}, store)
expect(status).not_to be_success
end
it "does not creates the record" do
expect(store).not_to receive(:create)
create_company({}, store)
end
it "returns a blank error" do
status = create_company({}, store)
expect(status.form.errors[:name]).to eq ["no puede estar en blanco"]
end
end
end
end
Is not the same, but is almost the same =)… and if you can build something like this, then you can use them in your controller like this…
class CompaniesController < ApplicationController
STORE = Company
def new
form = CRM.new_company_form
render locals: { form: form }
end
def create
status = CRM.create_company(params[:company], STORE)
if status.success?
redirect_to admin_companies_path, notice: "La empresa ha sido creado exitosamente."
else
render :new, locals: { form: status.form }
end
end
end
…with confidence that now you don’t need to test every path of your application on your controller or system test. Just write some integration tests when you feel the need to write them =).
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.