Ruby on Rails Coursera Class Bug in Tests - ruby-on-rails

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.

Related

How to avoid Rspec shared examples 'previously defined' warning?

I am trying to learn how to use Rspec's shared examples feature and am getting a warning when I run my tests:
WARNING: Shared example group 'required attributes' has been previously defined at:
/Users/me/app/spec/support/shared_examples/required_attributes_spec.rb:1
...and you are now defining it at:
/Users/me/app/spec/support/shared_examples/required_attributes_spec.rb:1
The new definition will overwrite the original one.
....
I have read what I think is the documentation on this problem here but I'm having trouble understanding it/seeing the takeaways for my case.
Here is my shared example:
# spec/support/shared_examples/required_attributes_spec.rb
shared_examples_for 'required attributes' do |arr|
arr.each do |meth|
it "is invalid without #{meth}" do
subject.send("#{meth}=", nil)
subject.valid?
expect(subject.errors[meth]).to eq(["can't be blank"])
end
end
end
I am trying to use this in a User model and a Company model. Here is what it looks like:
# spec/models/user_spec.rb
require 'rails_helper'
describe User do
subject { build(:user) }
include_examples 'required attributes', [:name]
end
# spec/models/company_spec.rb
require 'rails_helper'
describe Company do
subject { build(:company) }
include_examples 'required attributes', [:logo]
end
Per the recommendations in the Rspec docs I linked to above, I have tried changing include_examples to it_behaves_like, but that didn't help. I also commented out company_spec.rb entirely so there was just one spec using the shared example, and I am still getting the warning.
Can anyone help me see what's really going on here and what I should do in this case to avoid the warning?
I found the answer in this issue at the Rspec Github:
Just in case someone googles and lands here. If putting your file with
shared examples into support folder has not fixed the following
error...Make sure your filename does not end with _spec.rb.
As a followup to this, I had the issue in an included shared context with a filename that did not end in _spec.rb and was manually loaded via require_relative, not autoloaded. In my case, the issue was a copy-paste problem. The test looked like this:
RSpec.shared_examples 'foo' do
RSpec.shared_examples 'bar' do
it ... do... end
it ... do... end
# etc.
end
context 'first "foo" scenario' do
let ...
include_examples 'bar'
end
context 'second "foo" scenario' do
let ...
include_examples 'bar'
end
end
The intent was to provide a single shared set of examples that exercised multiple contexts of operation for good coverage, all running through that internal shared examples list.
The bug was simple but subtle. Since I have RSpec monkey patching turned off (disable_monkey_patching!), I have to use RSpec.<foo> at the top level. But inside any other RSpec blocks, using RSpec.<foo> defines the entity inside RSpec's top-level :main context. So, that second set of shared, "internal" examples weren't being defined inside 'foo', they were being defined at the top level. This confused things enough to trigger the RSpec warning as soon more than one other file require_relative'd the above code.
The fix was to just do shared_examples 'bar' in the nested set, not RSpec.shared_examples 'bar', so that the inner examples were correctly described inside the inner context.
(Aside: This is an interesting example of how having monkey patching turned off is more important than might at first glance appear to be the case - it's not just for namespace purity. It allows for a much cleaner distinction in declarations between "this is top level" and "this is nested".)

Passing validation tests in rails

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

Using Rspec to test ActiveRecord validations for similar fields

I recently started learning RoR and TDD, and am having trouble figuring out the best way to handle this scenario.
I have an ActiveRecord model with two fields which share the same validations.
How do I write an RSpec test which utilizes the same tests for the similar fields?
"shared examples" looked like a promising feature to utilize in this scenario, but does not seem to work, as I need to test the entire model but am only passing the individual field to the shared example.
Below is my failed attempt:
describe Trip do
before do
#trip = trip.new(date: '2013-07-01', city_1: "PORTLAND",
city_2: "BOSTON")
end
subject { #trip }
shared_examples "a city" do
describe "when not uppercase" do
before { city = city.downcase }
it { should_not be_valid }
end
end
describe "city_1 must be valid" do
it_should_behave_like "a city" do
let!(:city) { #trip.city_1}
end
end
describe "city_2 must be valid" do
it_behaves_like "a city" do
let!(:city) { #trip.city_2}
end
end
end
This fails because updating the city variable does not update trip model. Is there a way to dynamically tie it back to the model?
BTW, all the tests work on their own if I paste under each field. It just will not work in the context of the shared_example.
Any guidance would be greatly appreciated.
You can perform the assertion within a loop, and use a little metaprogramming, but I would advise against it. Tests should be as simple and straightforward as possible, even if that means having a little duplication. If only two fields are involved, just repeat it.

Best practice for reusing code in Rspec?

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

Is shoulda destroying my backtraces?

I have a test more or less like this:
class FormDefinitionTest < ActiveSupport::TestCase
context "a form_definition" do
setup do
#definition = SeedData.form_definition
# ...
I've purposely added a
raise "blah"
somewhere down the road and I get this error:
RuntimeError: blah
test/unit/form_definition_test.rb:79:in `__bind_1290079321_362430'
when I should be getting something along:
/Users/pupeno/projectx/db/seed/sheet_definitions.rb:17:in `sheet_definition': blah (RuntimeError)
from /Users/pupeno/projectx/db/seed/form_definitions.rb:4:in `form_definition'
from /Users/pupeno/projectx/test/unit/form_definition_test.rb:79
Any ideas what is sanitizing/destroying my backtraces? My suspicious is shoulda because the when the exception happens inside a setup or should is whet it happens.
This is a Rails 3 project, in case that's important.
That is because the shoulda method #context is generating code for you. for each #should block it generates a completely separate test for you so e.g.
class FormDefinitionTest < ActiveSupport::TestCase
context "a form_definition" do
setup do
#definition = SeedData.form_definition
end
should "verify some condition" do
assert something
end
should "verify some other condition" do
assert something_else
end
end
end
Then #should will generate two completely independent tests (for the two invocations of #should), one that executes
#definition = SeedData.form_definition
assert something
and another one that executes
#definition = SeedData.form_definition
assert something_else
It is worth noting that it does not generate one single test executing all three steps in a sequence.
These generated blocks of codes have method names like _bind_ something and the generated test have name that is a concatenation of all names of the contexts traversed to the should block plus the string provided by the should block (prefixed with "should "). There is another example in the documentation for shoulda-context.
I think this will give you the backtrace that you want. I haven't tested it, but it should work:
def exclude_backtrace_from_location(location)
begin
yeild
rescue => e
puts "Error of type #{e.class} with message: #{e.to_s}.\nBacktrace:"
back=e.backtrace
back.delete_if {|b| b~=/\A#{location}.+/}
puts back
end
end
exclude_backrace_from_location("test/unit") do
#some shoulda code that raises...
end
Have you checked config/initializers/backtrace_silencers.rb? That is the entry point to customize that behavior. With Rails.backtrace_cleaner.remove_silencers! you can cleanup the silencers stack.
More informations about ActiveSupport::BacktraceCleaner can be found here.

Resources