I'm learning Rspec for Rails and looking for a way to ensure that simply creating a class instance without a name provided will not succeed. How can I make this test below pass by making changes to the Dog class code?
class Dog < ActiveRecord::Base
validates_presence_of :dog_name
end
describe Dog do
it "requires a dog name to be created" do
dog = Dog.new(dog_name: nil)
expect(dog.save).to be_false
end
end
It would also be helpful to know how to write another test to verify that when a dog_name is set that the record created successfully.
I'd highly recommend checking out the shoulda-matchers gem. This would allow you to do these tests extremely easily. With shoulda-matchers, your tests would be:
describe Dog do
context 'validations' do
it { should validate_presence_of(:dog_name) }
...
end
end
There is nothing wrong with your test. It should pass if your system is set up correctly. You'll need to share the error you're getting if you want help in understanding why it's currently failing.
As for testing for successful creation, you would need to provide more information about what you've tried or what reference you are using that you don't understand in order to meet the SO question quality requirements.
describe Dog do
it "requires a dog name to be created" do
dog = Dog.create(dog_name: nil)
expect(dog).to have(1).error_on(:dog_name)
end
end
Related
I'm currently taking Coursera's free Ruby on Rails Introduction class. I'm working on the third assignment which contains creating a People class where you have some functionality like a search function.
I'm getting a weird error when I run rspec with their designed unit tests. I'm 99% sure the error is lying in the unit tests. Specifically, before I've even touched any files, I'm getting the following error:
raise <<-EOS
#{description} accessed in #{article} #{hook_expression} hook at:
#{CallerFilter.first_non_rspec_line}
`let` and `subject` declarations are not intended to be called
in #{article} #{hook_expression} hook, as they exist to define state that
is reset between each example, while #{hook_expression} exists to
#{hook_intention}.
EOS
RuntimeError:
let declaration `class` accessed in an `after(:context)` hook at:
/Users/<username>/.rvm/gems/ruby-2.4.0/gems/rspec-core-3.7.1/exe/rspec:4:in `<top (required)>'
`let` and `subject` declarations are not intended to be called
in an `after(:context)` hook, as they exist to define state that
is reset between each example, while `after(:context)` exists to
cleanup state that is shared across examples in an example group.
For starters, I don't totally understand the syntax they're using to describe talking about their tests. Secondly, here is the raw testing file that the author's of the Coursera class wrote:
require 'rspec'
require 'rspec/its'
require_relative '../module2_lesson3_formative.rb'
describe "lesson3" do
context "check results" do
p1 = Person.new("Ivana", "Trump")
p2 = Person.new("Eric", "Trump")
p3 = Person.new("Melania", "Trump")
p4 = Person.new("Marla", "Maples")
it "unexpected search result" do
expect(Person.search("Trump").size).to be == 3
end
end
context "check instance properties" do
subject(:john) { Person.new("Chris", "Christie") }
it "missing first_name" do
is_expected.to respond_to(:first_name)
end
it "missing last_name" do
is_expected.to respond_to(:last_name)
end
end
context "check class properties" do
subject(:class) { Person }
it "missing search" do
is_expected.to respond_to(:search)
end
end
end
I am hoping that someone can explain to me the debugging information when I run rspec. I'm using RSpec 3.7 which I'm guessing is the problem, as indicated that it might be a versioning upgrade thing here. That would also explain the fact that the class's authors didn't intentionally push up bad code. What is the best way for me to fix this and why are lines like this:
subject(:john) { Person.new("Chris", "Christie") }
in bad form? Thanks so much! Really appreciate your time :)
In order to change the subject class for your specs, then you can "redefine" subject per each of your examples, or as it's needed.
When trying to change the class of the spec subject, with let or subject, then you get the detailed error (warning) message:
let and subject declarations are not intended to be called in an
after(:context) hook, as they exist to define state that is reset
between each example
So you can't set explicitly the class of your subject, because it'll be resetted with each running example.
You can set your subject to be a Person object within the "check class properties" context by using just subject, this way is_expected will check in this object that responds to the class method search, like:
context "check class properties" do
subject { Person }
it 'missing search' do
is_expected.to respond_to(:search)
end
end
This seems to have resolved the problem that rspec was complaining about:
describe "lesson3" do
subject { person } # ADDED THIS LINE
context "check results" do
p1 = Person.new("Ivana", "Trump")
p2 = Person.new("Eric", "Trump")
p3 = Person.new("Melania", "Trump")
p4 = Person.new("Marla", "Maples")
it "unexpected search result" do
expect(Person.search("Trump").size).to be == 3
end
end
context "check instance properties" do
let(:person) { Person.new("Chris", "Christie") } # CHANGED THIS LINE
it "missing first_name" do
is_expected.to respond_to(:first_name)
end
it "missing last_name" do
is_expected.to respond_to(:last_name)
end
end
context "check class properties" do
let(:person) { Person } # CHANGED THIS LINE
it "missing search" do
is_expected.to respond_to(:search)
end
end
end
I still am not sure why, or what the difference is implying. I would love someone to explain it a little bit more in depth.
I'm following a TDD approach to building our app, and creating a whole bunch of service objects, keeping models strictly for data management.
Many of the services I've built interface with models. Take for example MakePrintsForRunner:
class MakePrintsForRunner
def initialize(runner)
#runner = runner
end
def from_run_report(run_report)
run_report.photos.each do |photo|
Print.create(photo: photo, subject: #runner)
end
end
end
I appreciate the create method could arguably be abstracted into the Print model, but let's keep it as is for now.
Now, in the spec for MakePrintsForRunner I'm keen to avoid including spec_helper, since I want my service specs to be super fast.
Instead, I stub out the Print class like this:
describe RunnerPhotos do
let(:runner) { double }
let(:photo_1) { double(id: 1) }
let(:photo_2) { double(id: 2) }
let(:run_report) { double(photos: [photo_1, photo_2]) }
before(:each) do
#service = RunnerPhotos.new(runner)
end
describe "#create_print_from_run_report(run_report)" do
before(:each) do
class Print; end
allow(Print).to receive(:create)
#service.create_print_from_run_report(run_report)
end
it "creates a print for every run report photo associating it with the runners" do
expect(Print).to have_received(:create).with(photo: photo_1, subject: runner)
expect(Print).to have_received(:create).with(photo: photo_2, subject: runner)
end
end
end
And all goes green. Perfect!
... Not so fast. When I run the whole test suite, depending on the seed order, I am now running into problems.
It appears that the class Print; end line can sometimes overwrite print.rb's definition of Print (which obviously inherits from ActiveRecord) and therefore fail a bunch of tests at various points in the suite. One example is:
NoMethodError:
undefined method 'reflect_on_association' for Print:Class
This makes for an unhappy suite.
Any advice on how to tackle this. While this is one example, there are numerous times where a service is directly referencing a model's method, and I've taken the above approach to stubbing them out. Is there a better way?
You don't have to create the Print class, simply use the one that is loaded, and stub it:
describe RunnerPhotos do
let(:runner) { double }
let(:photo_1) { double(id: 1) }
let(:photo_2) { double(id: 2) }
let(:run_report) { double(photos: [photo_1, photo_2]) }
before(:each) do
#service = RunnerPhotos.new(runner)
end
describe "#create_print_from_run_report(run_report)" do
before(:each) do
allow(Print).to receive(:create)
#service.create_print_from_run_report(run_report)
end
it "creates a print for every run report photo associating it with the runners" do
expect(Print).to have_received(:create).with(photo: photo_1, subject: runner)
expect(Print).to have_received(:create).with(photo: photo_2, subject: runner)
end
end
end
Edit
If you really need to create the class in the scope of this test alone, you can undefine it at the end of the test (from How to undefine class in Ruby?):
before(:all) do
unless Object.constants.include?(:Print)
class TempPrint; end
Print = TempPrint
end
end
after(:all) do
if Object.constants.include?(:TempPrint)
Object.send(:remove_const, :Print)
end
end
I appreciate the create method could arguably be abstracted into the Print model, but let's keep it as is for now.
Let's see what happens if we ignore this line.
Your difficulty in stubbing a class is a sign that the design is inflexible. Consider passing an already-instantiated object to either the constructor of MakePrintsForRunner or the method #from_run_report. Which to choose depends on the permanence of the object - will the configuration of printing need to change at run time? If not, pass to the constructor, if so, pass to the method.
So for our step 1:
class MakePrintsForRunner
def initialize(runner, printer)
#runner = runner
#printer = printer
end
def from_run_report(run_report)
run_report.photos.each do |photo|
#printer.print(photo: photo, subject: #runner)
end
end
end
Now it's interesting that we're passing two objects to the constructor, yet #runner is only ever passed to the #print method of #printer. This could be a sign that #runner doesn't belong here at all:
class MakePrints
def initialize(printer)
#printer = printer
end
def from_run_report(run_report)
run_report.photos.each do |photo|
#printer.print(photo)
end
end
end
We've simplified MakePrintsForRunner into MakePrints. This only takes a printer at construction time, and a report at method invocation time. The complexity of which runner to use is now the responsibility of the new 'printer' role.
Note that the printer is a role, not necessarily a single class. You can swap the implementation for different printing strategies.
Testing should now be simpler:
photo1 = double('photo')
photo2 = double('photo')
run_report = double('run report', photos: [photo1, photo2])
printer = double('printer')
action = MakePrints.new(printer)
allow(printer).to receive(:print)
action.from_run_report(run_report)
expect(printer).to have_received(:print).with(photo1)
expect(printer).to have_received(:print).with(photo2)
These changes might not suit your domain. Perhaps a runner shouldn't be attached to a printer for more than one print. In this case, perhaps you should take a different next step.
Another future refactoring might be for #from_run_report to become #from_photos, since the report isn't used for anything but gathering photos. At this point the class looks a bit anaemic, and might disappear altogether (eaching over photos and calling #print isn't too interesting).
Now, how to test a printer? Integrate with ActiveRecord. This is your adapter to the outside world, and as such should be integration tested. If all it really does is create a record, I probably wouldn't even bother testing it - it's just a wrapper around an ActiveRecord call.
Class names are just constants so you could use stub_const to stub an undefined constant and return a double.
So instead of defining a class in your before(:each) block do this:
before(:each) do
stub_const('Print', double(create: nil))
#service.create_print_from_run_report(run_report)
end
I'm writing integration tests using Rspec and Capybara. I've noticed that quite often I have to execute the same bits of code when it comes to testing the creation of activerecord options.
For instance:
it "should create a new instance" do
# I create an instance here
end
it "should do something based on a new instance" do
# I create an instance here
# I click into the record and add a sub record, or something else
end
The problem seems to be that ActiveRecord objects aren't persisted across tests, however Capybara by default maintains the same session in a spec (weirdness).
I could mock these records, but since this is an integration test and some of these records are pretty complicated (they have image attachments and whatnot) it's much simpler to use Capybara and fill out the user-facing forms.
I've tried defining a function that creates a new record, but that doesn't feel right for some reason. What's the best practice for this?
There are a couple different ways to go here. First of all, in both cases, you can group your example blocks under either a describe or context block, like this:
describe "your instance" do
it "..." do
# do stuff here
end
it "..." do
# do other stuff here
end
end
Then, within the describe or context block, you can set up state that can be used in all the examples, like this:
describe "your instance" do
# run before each example block under the describe block
before(:each) do
# I create an instance here
end
it "creates a new instance" do
# do stuff here
end
it "do something based on a new instance" do
# do other stuff here
end
end
As an alternative to the before(:each) block, you can also use let helper, which I find a little more readable. You can see more about it here.
The very best practice for your requirements is to use Factory Girl for creating records from a blueprint which define common attributes and database_cleaner to clean database across different tests/specs.
And never keep state (such as created records) across different specs, it will lead to dependent specs. You could spot this kind of dependencies using the --order rand option of rspec. If your specs fails randomly you have this kind of issue.
Given the title (...reusing code in Rspec) I suggest the reading of RSpec custom matchers in the "Ruby on Rails Tutorial".
Michael Hartl suggests two solutions to duplication in specs:
Define helper methods for common operations (e.g. log in a user)
Define custom matchers
Use these stuff help decoupling the tests from the implementation.
In addition to these I suggest (as Fabio said) to use FactoryGirl.
You could check my sample rails project. You could find there: https://github.com/lucassus/locomotive
how to use factory_girl
some examples of custom matchers and macros (in spec/support)
how to use shared_examples
and finally how to use very nice shoulda-macros
I would use a combination of factory_girl and Rspec's let method:
describe User do
let(:user) { create :user } # 'create' is a factory_girl method, that will save a new user in the test database
it "should be able to run" do
user.run.should be_true
end
it "should not be able to walk" do
user.walk.should be_false
end
end
# spec/factories/users.rb
FactoryGirl.define do
factory :user do
email { Faker::Internet.email }
username { Faker::Internet.user_name }
end
end
This allows you to do great stuff like this:
describe User do
let(:user) { create :user, attributes }
let(:attributes) { Hash.new }
it "should be able to run" do
user.run.should be_true
end
it "should not be able to walk" do
user.walk.should be_false
end
context "when user is admin" do
let(:attributes) { { admin: true } }
it "should be able to walk" do
user.walk.should be_true
end
end
end
I am finding it very hard to stub certain attributes of a model on a controller test. I want to make sure to stub as little as possible.
EDIT: I have been demoved of using stubs for such integration. I understood that the stubs won't reach the action call. The correct question would now be:
How can one use mocks and stubs to simulate a certain state in a Rails controller test?
So I've reached something like the following:
Spec
require 'spec_helper'
describe TeamsController do
let(:team) { FactoryGirl.create :team }
context "having questions" do
let(:competition) { FactoryGirl.create :competition }
it "allows a team to enter a competition" do
post(:enter_competition, id: team.id, competition_id: competition.id)
assigns(:enroll).team.should == team
assigns(:enroll).competition.should == competition
end
end
# ...
end
Factories
FactoryGirl.define do
factory :team do
name "Ruby team"
end
factory :competition, class: Competition do
name "Competition with questions"
after_create do |competition|
competition.
stub(:questions).
and_return([
"something"
])
end
end
factory :empty_competition, class: Competition do
name "Competition without questions"
questions []
after_create do |competition|
competition.stub(:questions).and_return []
end
end
end
Production code
class TeamsController < ApplicationController
def enter_competition
#team = Team.find params[:id]
#competition = Competition.find params[:competition_id]
#enroll = #team.enter_competition #competition
render :nothing => true
end
end
class Team < ActiveRecord::Base
def enter_competition competition
raise Competition::Closed if competition.questions.empty?
enroll = Enroll.new team: self, competition: competition
enroll.save
enroll
end
end
When I run the test, the questions attribute comes as being nil and so the test fails in the model when checking for nil.empty?.
Why isn't the stub being used so that the state of that message is correctly used? I expected that #competition.questions would be [ "question" ] but instead I get nil.
The problem you're running into is that stub works on an instance of a Ruby object; it doesn't affect all ActiveRecord objects that represent the same row.
The quickest way to fix your test would be to add this to your test, before the post:
Competition.stub(:find).and_return(competition)
The reason that's necessary is that Competition.find will return a fresh Competition object that doesn't have questions stubbed out, even though it represents the same database row. Stubbing find as well means that it will return the same instance of Competition, which means the controller will see the stubbed questions.
I'd advise against having that stub in your factory, though, because it won't be obvious what's stubbed as a developer using the factory, and because it means you'll never be able to test the real questions method, which you'll want to do in the Competition unit test as well as any integration tests.
Long story short: if you stub out a method on an instance of your model, you also need to stub out find for that model (or whatever class method you're using to find it), but it's not a good idea to have such stubs in a factory definition.
When you call create on FactoryGirl, it creates database records which you then retrieve back in your controller code. So the instances you get (#team, #competition) are pure ActiveRecord, without any methods stubbed out.
Personally I would write you test like this (not touching database at all):
let(:team) { mock_model(Team) }
let(:competition) { mock_model(Competition) }
before do
Team.stub(:find) { team }
Competition.stub(:find) { competition }
end
and then in your test something like this:
it "should call enter_competition on #team with #competition" do
team.should_receive(:enter_competition).with(competition)
post :enter_competition, id: 7, competition_id: 10
I don't really understand what your controller is supposed to do or what are you testing for that matter, sorry :(
I would like to implement the method User.calculate_hashed_password. I'm trying to use the Shoulda testing library which works with Rails's built-in testing tools, so an answer related to Test::Unit would be just as good as one related to Shoulda (I think).
I'm trying to figure out what I need to test and how I should test it. My initial idea is to do something like...
class UserTest < ActiveSupport::TestCase
should 'Return a hashed password'
assert_not_nil User.calculate_hashed_password
end
end
Is this the right way to do it?
You don't need to test that the method exists, just that the method behaves correctly. Say something like this:
class UserTest < ActiveSupport::TestCase
setup do
#user = User.new
end
should 'Calculate the hashed password correctly'
#user.password = "password"
#user.hashed_password = "xxxxx" # Manually calculate it
end
end
(I don't use shoulda, so excuse any glaring syntax errors.)
That test will fail if the method doesn't exist.
I agree with Otto; but as dylanfm noted, I use #respond_to to test for associations in RSpec.
it "should know about associated Projects" do
#user.should respond_to(:projects)
end
Maybe use respond_to?
You should check out Object#respond_to? and Object#try in newer versions of Rails. If you're new to testing in general, definitely read through this excellent guide on testing in Rails.