Here's my integration spec. I would like to define those users at the global scope so I can make expectations based on them in all of the spec's expectations. However, doing this means the variables #user_0 and the rest aren't available inside the expectation.
Notice I've got that ap #user_0 in the first expectation to print the hash. It returns nil.
First question is, how do I make globally available variables in an integration spec?
It's worth noting that if I build the variables in the expectation, everything works fine then, it's just when the variables are built outside the expectation.
Am I right in thinking that this is an error? I mean surely I'm going to want to write expectations that use users defined in other specs? Or is each spec a sort of self contained thing, and if transitional fixtures are turned on, the test database is wiped between each expectation? It's just odd that these instance variables aren't scoped by default, as though it's been done on purpose.
require 'spec_helper'
require 'factory_girl'
#user_0 = FactoryGirl.build(:user_0)
#user_1 = FactoryGirl.build(:user_1)
#user_2 = FactoryGirl.build(:user_2)
#user_3 = FactoryGirl.build(:user_3)
describe "foo", js: true do
it "can create a user" do
ap #user_0 #=> nil
end
end
Add call to :before method to evaluate the specified block before each it operation:
require 'spec_helper'
require 'factory_girl'
describe "foo", js: true do
before :each do
#user_0 = FactoryGirl.build(:user_0)
#user_1 = FactoryGirl.build(:user_1)
#user_2 = FactoryGirl.build(:user_2)
#user_3 = FactoryGirl.build(:user_3)
end
it "can create a user" do
ap #user_0 #=> nil
end
end
And make sure that your user factory has been properly written.
Related
I am new to Rspec. I am writing a test case to cover some action in a model. Here is my rspec code
test_cover_image_spec.rb
require 'spec_helper'
describe Issue do
before :each do
#issue = Issue.joins(:multimedia).uniq.first
binding.pry
end
describe '#release_cover_image' do
context 'While making an issue open' do
it 'should make issue cover in S3 accessible' do
put :update, :id => #issue.id, :issue => #issue.attributes = {:open => '1'}
end
end
end
end
#issue always returns nil. In my debugger also, Issue.all returns an empty array.
Tests usually run in isolation. That means each test needs to set up the objects before running. After the test run common test configurations delete all created data from the test database. That means you need to create your test data before you can use it.
For example like this:
require 'spec_helper'
describe Issue do
# pass all attributes to create a valid issue
let(:issue) { Issue.create(title: 'Foo Bar') }
describe '#release_cover_image' do
context 'While making an issue open' do
it 'should make issue cover in S3 accessible' do
put :update, id: issue.id, issue: { open: '1' }
expect(issue.reload.open).to eq('1')
end
end
end
end
To make this work you have to populate the test database first.
Check out factory_girl gem - it is most often used for easy generating test data.
So (general idea is that) you will have to create few factories:
issues_factory.rb
multimedia_factory.rb
And use them, to generate the issue object prior the test run.
If you're not going to use factory_girl then anyway you should change from creating an issue in before block to using let:
let(:issue) { Issue.create }
I have this code that I want to reuse in several specs:
RSpec.shared_context "a UserWorker" do |user|
let(:mock_context_user) {{
id: 1,
brand: user.brand,
backend_token: user.backend_token
}}
before(:each) do
allow(SomeClass).to receive(:some_method)
.with(user.id).and_return(mock_context_user)
end
before(:each, context: true) do
Sidekiq::Testing.inline!
end
after(:each, context: true) do
Sidekiq::Testing.fake!
end
end
And in the spec file that uses the shared code:
let(:user) { build :user } # FactoryGirl
...
describe '#perform' do
# some lets here
include_context 'a UserWorker', user
context 'when something exists' do
it 'does some stuff' do
# test some stuff here
end
end
end
But that gives me this error:
/.rvm/gems/ruby-2.3.0#fb-cont/gems/rspec-core-3.5.1/lib/rspec/core/example_group.rb:724:in `method_missing': `user` is not available on an example group (e.g. a `describe` or `context` block). It is only available from within individual examples (e.g. `it` blocks) or from constructs that run in the scope of an example (e.g. `before`, `let`, etc). (RSpec::Core::ExampleGroup::WrongScopeError)
Suggestions? Any help is appreciated.
The RSpec docs aren't very clear on this, but you can inject additional values by passing a block containing let() calls to include_context. The "customization block" passed by the spec will be evaluated first, and is available to the code declared in the shared context.
Here's a shared context that depends on the specs including it to let() a value, value_from_spec, and then sets a couple more values, one via let() and one via a before() block:
RSpec.shared_context('a context', shared_context: :metadata) do
# assume the existence of value_from_spec
let(:a_value_from_context) { value_from_spec - 1 }
before(:each) do
# assume the existence of value_from_spec
#another_value_from_context = value_from_spec + 1
end
end
(Note that unlike the OP's |user| example, we never explicitly declare value_from_spec, we just trust that it'll be there when we need it. If you want to make what's going on more obvious, you could check defined?(:value_from_spec) and raise an error.)
And here's a spec that injects that value, and reads the shared context's transformations of it:
describe 'passing values to shared context with let()' do
# "customization block"
include_context 'a context' do
# set value_from_spec here
let(:value_from_spec) { 1 }
end
describe 'the context' do
it 'should read the passed value in a let() block' do
expect(a_value_from_context).to eq(0)
end
it 'should read the passed value in a before() block' do
expect(#another_value_from_context).to eq(2)
end
end
end
Since it will always return the same mock_context_user, you can try something more generic like:
allow(SomeClass)
.to receive(:some_method)
.with(an_instance_of(Fixnum))
.and_return(mock_context_user)
But I'm not actually sure if an_instance_of is available for RSpec 3.5, it is on RSpec 3.3.
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'm a little lost with RSpec having always stuck to xUnit-based testing frameworks, but I'm giving it a go.
The nested nature of the way specs are written is giving me some headaches with regards to where I'm supposed to do database setup/teardown though.
According the to DatabaseCleaner README:
Spec::Runner.configure do |config|
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
Now I can't use transactions, because I use them in my code, so I'm just sticking to truncation, but that should be neither here nor there.
I have this:
RSpec.configure do |config|
config.mock_with :rspec
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
The problem here is that any fixtures I create in a subject or let block have already disappeared (from the database) when I try to use them in a following describe or it block.
For example (using Machinist to create the fixtures... but that shouldn't be relevant):
describe User do
describe "finding with login credentials" do
let(:user) { User.make(:username => "test", :password => "password") }
subject { User.get_by_login_credentials!("test", "password") }
it { should == user }
end
end
I'm struggling with how I'm supposed to be nesting these describe and subject and other blocks, so maybe that's my problem, but basically this fails because when it tries to get the user from the database, it's already been removed due to the after(:each) hook being invoked, presumably after the let?
If you're going to use subject and let together, you need to understand how/when they are invoked. In this case, subject is invoked before the user method generated by let. The problem is not that the object is removed from the db before subject is invoked, but that it is not even created at that point.
Your example would work if you use the let! method, which adds a before hook that implicitly invokes the user method before the example (and therefore before subject is invoked).
That said, I'd recommend you stop struggling and use simpler API's that RSpec already exposes:
describe User do
it "finds a user with login credentials" do
user = User.make(:username => "test", :password => "password")
User.get_by_login_credentials!("test", "password").should eq(user)
end
end
That seems much simpler to me.
You wrote:
The problem here is that any fixtures I create in a subject or let block have already disappeared (from the database) when I try to use them in a following describe or it block.
That's right, that's how it works. (And you're not using fixtures in the usual Rails sense, but factories -- just as well, since Rails fixtures suck.)
Every individual spec (that is, every it block) starts (or should start) from a pristine database. Otherwise your tests would leak state and lose atomicity. So you should create every record you need within the spec in which you need it (or, as David said, in a before block to cut down on repetition).
As for organizing your specs...do it any way that makes sense. Usually there will be an outer describe block for the whole class, with inner describe blocks for groups of related behavior or specs that need a common setup. Each describe context can have its own before and after blocks. These get nested as you would expect, so the order of execution is something like
outer before
inner before
spec
inner after
outer after
If you'd like to see a project with a large number of RSpec specs and Cucumber stories (though for slightly old versions of each), check out http://github.com/marnen/quorum2 .
I want to make sure my sweeper is being called as appropriate so I tried adding something like this:
it "should clear the cache" do
#foo = Foo.new(#create_params)
Foo.should_receive(:new).with(#create_params).and_return(#foo)
FooSweeper.should_receive(:after_save).with(#foo)
post :create, #create_params
end
But I just get:
<FooSweeper (class)> expected :after_save with (...) once, but received it 0 times
I've tried turning on caching in the test config but that didn't make any difference.
As you already mentioned caching has to be enabled in the environment for this to work. If it's disabled then my example below will fail. It's probably a good idea to temporarily enable this at runtime for your caching specs.
'after_save' is an instance method. You setup an expectation for a class method, which is why it's failing.
The following is the best way I've found to set this expectation:
it "should clear the cache" do
#foo = Foo.new(#create_params)
Foo.should_receive(:new).with(#create_params).and_return(#foo)
foo_sweeper = mock('FooSweeper')
foo_sweeper.stub!(:update)
foo_sweeper.should_receive(:update).with(:after_save, #foo)
Foo.instance_variable_set(:#observer_peers, [foo_sweeper])
post :create, #create_params
end
The problem is that Foo's observers (sweepers are a subclass of observers) are set when Rails boots up, so we have to insert our sweeper mock directly into the model with 'instance_variable_set'.
Sweepers are Singletons and are instantiated at the beginning of the rspec test. As such you can get to it via MySweeperClass.instance(). This worked for me (Rails 3.2):
require 'spec_helper'
describe WidgetSweeper do
it 'should work on create' do
user1 = FactoryGirl.create(:user)
sweeper = WidgetSweeper.instance
sweeper.should_receive :after_save
user1.widgets.create thingie: Faker::Lorem.words.join("")
end
end
Assuming you have:
a FooSweeper class
a Foo class with a bar attribute
foo_sweeper_spec.rb:
require 'spec_helper'
describe FooSweeper do
describe "expiring the foo cache" do
let(:foo) { FactoryGirl.create(:foo) }
let(:sweeper) { FooSweeper.instance }
it "is expired when a foo is updated" do
sweeper.should_receive(:after_update)
foo.update_attribute(:bar, "Test")
end
end
end