rspec model spec let vs factory - ruby-on-rails

I'm starting out with testing and it's not exactly clear when I should use let.
Should I use let in the following model testing for lazy loading or since the data is bit different in every test I can keep it as it is? As I've seen in some examples it's way more important for controller testing as the :task would be the same for each action test.
model spec
require 'rails_helper'
RSpec.describe Task, type: :model do
describe "model validations" do
it "has a valid factory" do
expect(build(:task)).to be_valid
end
it "is invalid without executor" do
expect(build(:task, executor_id: nil)).not_to be_valid
end
it "is invalid without assigner" do
expect(build(:task, assigner_id: nil)).not_to be_valid
end
it "is invalid without content" do
expect(build(:task, content: nil)).not_to be_valid
end
it "is invalid without deadline" do
expect(build(:task, deadline: nil)).not_to be_valid
end
it "is invalid with deadline in the past" do
expect(build(:task, deadline: Faker::Time.between(DateTime.now - 1, DateTime.now - 2))).not_to be_valid
end
end
end
factories
FactoryGirl.define do
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
end
end

The benefits of let come from tests that aren't of the form you use above. Imagine this group:
context "completing tasks" do
let(:completing_a_task){ task.complete }
context "that aren't due yet" do
let(:task){ create(:not_due_task) }
it "should not send an email" do
expect( TaskMailer ).not_to receive(:deliver_message)
expect{ completing_a_task }.not_to raise_error
end
end
context "overdue" do
let(:task){ create(:overdue_task) }
it "should send an email" do
expect( TaskMailer ).to receive(:deliver_message)
expect{ completing_a_task }.not_to raise_error
end
end
end
By allowing for late binding, you can make minimal changes but provide maximal coverage. The more collaborators you need in order to set up the appropriate behavior needed for your tests, the more likely you are to benefit from let. While it's try that you don't particularly need to drive for DRY in your test suite, huge setup blocks for your tests are a smell, and the let technique is a great tool to help fight for clarity and simplicity even when your domain presents complexity. My own example includes no collaborators still, but hopefully the concept is still clear enough.

I'd suggest keeping it how you have it and not using a let. Don't worry about DRYing up your tests. They don't interact with each other, and so you don't run into the issues you would with code duplication in application logic.
For what it's worth, you can use the shoulda-matchers gem to accomplish what you've got there: https://github.com/thoughtbot/shoulda-matchers
describe Task do
describe "validations" do
it { is_expected.to validate_presence_of(:content) }
it { is_expected.to validate_presence_of(:deadline) }
end
end

Related

Avoid excessive rspec nesting with subject, let, and alternative arguments

I'm trying to do some model_spec testing but having trouble with not having to further nest my rspec code. It would be great if in this case, I could just have a set of "it's" instead of having to add context everytime I want to switch the variable var. Here's the following code:
describe "#some_method" do
subject { course.some_method(var) }
context 'given a project' do
let(:var) {random[1]}
it 'returns the one after' do
is_expected.to eq(random[2])
end
context 'being the last' do
let(:vars) {random.last}
it 'returns nil' do
is_expected.to be_nil
end
end
context '...you get the point, being something else' do
let(:vars) { something.else }
it 'returns nil' do
is_expected.to.to be_nil
end
end
end
end
Maybe I'm just stuck in the wrong mode of thinking and someone could think of a better way for me to do this? I've been suggested that I absolutely must use the subject by someone I work for.
At first, I disagreed and thought it was getting a little burdensome but then I figured keeping subject and having let(:var) apply to it was pretty useful...
RSpecs subject is a tool which can be used to make tests more succinct. There are many cases where it makes sense to use the subject:
RSpec.describe User do
# with the help of shoulda-matchers
it { should validate_uniqueness_of :username } # implicit subject
end
RSpec.describe UsersController do
describe '#show' do
it 'is successful' do
get :show
expect(response).to have_http_status :success
end
it 'renders template show' do
get :show
expect(response).to render_template :show
end
end
#vs
describe '#show' do
subject { response }
before { get :show }
it { should have_http_status :success }
it { should render_template :success }
end
end
And there are cases where using subject will hurt the readability and acuity of your tests.
Your college is just plain wrong in insisting that you always use subject.
A good rule of hand is that if you need an it block then you should not be using subject or is_expected.
If you are describing the call signature of a method you should be calling it in your specs in the same way you would in real life.
let(:decorator){ described_class.new(user) }
describe "#link" do
it 'takes a class option' do
expect(decorator.link(class: 'button')).to match /class=\"button/
end
end
I would recommend running rspec with the --format documentation option and checking if the output actually makes sense. This can be quite important once you get 100s of specs as it gets harder to remember what a behavior a spec actually covers.
How about you write it like this?
expect(subject.call(foo)) is not very pretty but it gets rid of the nesting.
describe "#some_method" do
subject { course.method(:some_method) }
it 'returns the one after if given a project' do
expect(subject.call(random[1])).to eq(random[2])
end
it 'returns nil when it is the last' do
expect(subject.call(random.last)).to be_nil
end
it 'returns nil...' do
expect(subject.call(something.else)).to be_nil
end
end

Rspec/Capybara cant find my model when trying to use "should_not change"

I am trying to verify that the validators are working correctly on my model, and for that I am using Rspec and Capybara. Here is my code.
describe "#when registering" do
before { visit new_record_path }
describe "#with invalid information" do
describe "#should not modify database" do
subject { -> { click_button submit } }
it { should_not change(Pet, :count) }
it { should_not change(Owner, :count) }
end
end
end
end
When I run the specs, i get an error: "undefined method 'model_name' for NilClass:Class"
What could be causing rspec to think my model is nil?
Thanks!
You should not test your validations with a feature/acceptance test, it should be with a model test. Then for each form you could test an error is raised if something is invalid instead of testing every error through acceptance tests. For each model it should be something like so:
describe Pet do
describe "validations" do
# These can echo any model validation
it "is invalid if attribute is not present" do
Pet.new(:attribute => "Invalid Item").should_not be_valid
end
end
end
or with Factory Girl:
describe Pet do
describe "validations" do
it "is invalid if attribute is not present" do
build(:pet, :attribute => "Invalid Item").should_not be_valid
end
end
end
Then in an acceptance test you can have something like:
it "displays an error if validation fails" do
visit new_pet_path
#Something to make the form submission fail, not everything
fill_in("Attribute", :with => "")
click_button("Create Pet")
page.should have_content("can't be blank")
current_path.should == pets_path
end
This will help to keep your acceptance tests light and test the validations in the model where it belongs. Hope this helps!

Rspec and testing instance methods

Here is my rspec file:
require 'spec_helper'
describe Classroom, focus: true do
describe "associations" do
it { should belong_to(:user) }
end
describe "validations" do
it { should validate_presence_of(:user) }
end
describe "instance methods" do
describe "archive!" do
before(:each) do
#classroom = build_stubbed(:classroom)
end
context "when a classroom is active" do
it "should mark classroom as inactive" do
#classroom.archive!
#classroom.active.should_be == false
end
end
end
end
end
Here is my Classroom Factory:
FactoryGirl.define do
factory :classroom do
name "Hello World"
active true
trait :archive do
active false
end
end
end
When the instance method test runs above, I receive the following error: stubbed models are not allowed to access the database
I understand why this is happening (but my lack of test knowledge/being a newb to testing) but can't figure out how to stub out the model so that it doesn't hit the database
Working Rspec Tests:
require 'spec_helper'
describe Classroom, focus: true do
let(:classroom) { build(:classroom) }
describe "associations" do
it { should belong_to(:user) }
end
describe "validations" do
it { should validate_presence_of(:user) }
end
describe "instance methods" do
describe "archive!" do
context "when a classroom is active" do
it "should mark classroom as inactive" do
classroom.archive!
classroom.active == false
end
end
end
end
end
Your archive! method is trying to save the model to the database. And since you created it as a stubbed model, it doesn't know how to do this. You have 2 possible solutions for this:
Change your method to archive, don't save it to the database, and call that method in your spec instead.
Don't use a stubbed model in your test.
Thoughtbot provides a good example of stubbing dependencies here. The subject under test (OrderProcessor) is a bona fide object, while the items passed through it are stubbed for efficiency.

Rails 3.1, RSpec: testing model validations

I have started my journey with TDD in Rails and have run into a small issue regarding tests for model validations that I can't seem to find a solution to. Let's say I have a User model,
class User < ActiveRecord::Base
validates :username, :presence => true
end
and a simple test
it "should require a username" do
User.new(:username => "").should_not be_valid
end
This correctly tests the presence validation, but what if I want to be more specific? For example, testing full_messages on the errors object..
it "should require a username" do
user = User.create(:username => "")
user.errors[:username].should ~= /can't be blank/
end
My concern about the initial attempt (using should_not be_valid) is that RSpec won't produce a descriptive error message. It simply says "expected valid? to return false, got true." However, the second test example has a minor drawback: it uses the create method instead of the new method in order to get at the errors object.
I would like my tests to be more specific about what they're testing, but at the same time not have to touch a database.
Anyone have any input?
CONGRATULATIONS on you endeavor into TDD with ROR I promise once you get going you will not look back.
The simplest quick and dirty solution will be to generate a new valid model before each of your tests like this:
before(:each) do
#user = User.new
#user.username = "a valid username"
end
BUT what I suggest is you set up factories for all your models that will generate a valid model for you automatically and then you can muddle with individual attributes and see if your validation. I like to use FactoryGirl for this:
Basically once you get set up your test would look something like this:
it "should have valid factory" do
FactoryGirl.build(:user).should be_valid
end
it "should require a username" do
FactoryGirl.build(:user, :username => "").should_not be_valid
end
Here is a good railscast that explains it all better than me:
UPDATE: As of version 3.0 the syntax for factory girl has changed. I have amended my sample code to reflect this.
An easier way to test model validations (and a lot more of active-record) is to use a gem like shoulda or remarkable.
They will allow to the test as follows:
describe User
it { should validate_presence_of :name }
end
Try this:
it "should require a username" do
user = User.create(:username => "")
user.valid?
user.errors.should have_key(:username)
end
in new version rspec, you should use expect instead should, otherwise you'll get warning:
it "should have valid factory" do
expect(FactoryGirl.build(:user)).to be_valid
end
it "should require a username" do
expect(FactoryGirl.build(:user, :username => "")).not_to be_valid
end
I have traditionally handled error content specs in feature or request specs. So, for instance, I have a similar spec which I'll condense below:
Feature Spec Example
before(:each) { visit_order_path }
scenario 'with invalid (empty) description' , :js => :true do
add_empty_task #this line is defined in my spec_helper
expect(page).to have_content("can't be blank")
So then, I have my model spec testing whether something is valid, but then my feature spec which tests the exact output of the error message. FYI, these feature specs require Capybara which can be found here.
Like #nathanvda said, I would take advantage of Thoughtbot's Shoulda Matchers gem. With that rocking, you can write your test in the following manner as to test for presence, as well as any custom error message.
RSpec.describe User do
describe 'User validations' do
let(:message) { "I pitty da foo who dont enter a name" }
it 'validates presence and message' do
is_expected.to validate_presence_of(:name).
with_message message
end
# shorthand syntax:
it { is_expected.to validate_presence_of(:name).with_message message }
end
end
A little late to the party here, but if you don't want to add shoulda matchers, this should work with rspec-rails and factorybot:
# ./spec/factories/user.rb
FactoryBot.define do
factory :user do
sequence(:username) { |n| "user_#{n}" }
end
end
# ./spec/models/user_spec.rb
describe User, type: :model do
context 'without a username' do
let(:user) { create :user, username: nil }
it "should NOT be valid with a username error" do
expect(user).not_to be_valid
expect(user.errors).to have_key(:username)
end
end
end

Rspec Rails - Name should be valid - some clarifications

i'm into rspec these days, trying to make my models more precise and accurate. Some things are still a bit weird to me about rspec and so i thought it would be nice if someone could clarify.
Let's say that i have a User model. This one has a :name. The name should be between 4..15 characters(that's a secondary objective, at first it must just exist). So now i'm thinking: What is the best way to test that in a manner that assures that this will happen. To test that a user must have a name, i wrote something like this :
describe User do
let(:user) { User.new(:name => 'lele') }
it "is not valid without a name" do
user.name.should == 'lele'
end
end
Now, i'm not quite sure that this accomplishes exactly what i want. It seems to me that i'm actually testing Rails with this one. Moreover, if i want to check that a name cannot be more than 15 chars and less than 4, how can this be integrated ?
EDIT:
Maybe this is better ?
describe User do
let(:user) { User.new(:name => 'lele') }
it "is not valid without a name" do
user.name.should_not be_empty
end
end
You're probably looking for the be_valid matcher:
describe User do
let(:user) { User.new(:name => 'lele') }
it "is valid with a name" do
user.should be_valid
end
it "is not valid without a name" do
user.name = nil
user.should_not be_valid
end
end
I use this way:
describe User do
it "should have name" do
lambda{User.create! :name => nil}.should raise_error
end
it "is not valid when the name is longer than 15 characters" do
lambda{User.create! :name => "im a very looooooooong name"}.should raise_error
end
it "is not valid when the name is shorter than 4 characters" do
lambda{User.create! :name => "Tom"}.should raise_error
end
end
I like to test the actual error messages for validations:
require 'spec_helper'
describe User do
let (:user) { User.new }
it "is invalid without a name" do
user.valid?
user.errors[:name].should include("can't be blank")
end
it "is invalid when less than 4 characters" do
user.name = "Foo"
user.valid?
user.errors[:name].should include("is too short (minimum is 4 characters)")
end
it "is invalid when greater than 15 characters" do
user.name = "A very, very, very long name"
user.valid?
user.errors[:name].should include("is too long (maximum is 15 characters)")
end
end
It's also helpful to use a factory that builds an object with valid attributes, which you can invalidate one at a time for testing.
I would use something similar to this
class User < ActiveRecord::Base
validates_presence_of :name
validates_length_of :name, :in => 4..15
end
describe User do
it "validates presence of name" do
user = User.new
user.valid?.should be_false
user.name = "valid name"
user.valid?.should be_true
end
it "validates length of name in 4..15" do
user = User.new
user.name = "123"
user.valid?.should be_false
user.name = "1234567890123456"
user.valid?.should be_false
user.name = "valid name"
user.valid?.should be_true
end
end
Most notable is that I'm using active record validations for both conditions. In my examples I don't rely on the error strings. In examples that test the behavior of validations there's no reason to touch the database so I don't. In each example I test the behavior of the object when it's both valid and invalid.

Resources