I have a controller spec like this :
describe "#create" do
before { post 'create', params }
context "when the artist is valid" do
before { allow(artist).to receive(:save).and_return(true) }
it { expect(page).to redirect_to(root_path) }
it { expect(notifier).to have_received(:notify) }
end
end
This is a simple spec but It doesn't work because the describe's before block is executed before the context's before block. So, the result of artist.save is not stubed when the create action is called.
It tried to do this :
describe "first describe" do
before { puts 2 }
describe "second describe" do
before { puts 1 }
it "simple spec" do
expect(1).to eq 1
end
end
end
I see the "2" before the "1". I'm not sure but I think it was working with previous versions.
I know, I can do this :
describe "#create" do
context "when the artist is valid" do
before { allow(artist).to receive(:save).and_return(true) }
it "redirect to the root path" do
post 'create', params
expect(page).to redirect_to(root_path)
end
it "do notifications" do
post :create, params
expect(notifier).to have_received(:notify)
end
end
end
But I think it's less clean.
I found, on this page, http://rubydoc.info/github/rspec/rspec-core/RSpec/Core/Hooks#before-instance_method than the order should be this :
before(:suite) # declared in RSpec.configure
before(:all) # declared in RSpec.configure
before(:all) # declared in a parent group
before(:all) # declared in the current group
before(:each) # declared in RSpec.configure
before(:each) # declared in a parent group
before(:each) # declared in the current group
It's not the case on this example.
I'm not sure but I think it was working with older versions of rspec.
Is there a solution?
I would strongly recommend against you changing the order of hooks in rspec. That will make your app non-standard and Rails is build on standards and having things work as expected.
Everything you're describing it "as designed". Outer before blocks are always called before inner blocks.
Your example that you feel is "less clean" is the standard way to do controller specs. I actually encourage you to do it this way so that it is more maintainable/readable. It does not look unclean to me at all.
That said, there are some options:
You can use a method. I have more than once had a method that was do_post or something similar
You can use a let block which is initialized lazily. I would find it unlcean if it relied on other before blocks running first, but it's an option.
You can define subject. https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/subject/explicit-subject
Related
Let's say I have various RSpec context blocks to group tests with similar data scenarios.
feature "User Profile" do
context "user is active" do
before(:each) { (some setup) }
# Various tests
...
end
context "user is pending" do
before(:each) { (some setup) }
# Various tests
...
end
context "user is deactivated" do
before(:each) { (some setup) }
# Various tests
...
end
end
Now I'm adding a new feature and I'd like to add a simple scenario that verifies behavior when I click a certain link on the user's page
it "clicking help redirects to the user's help page" do
click_on foo_button
expect(response).to have('bar')
end
Ideally I'd love to add this test for all 3 contexts because I want to be sure that it performs correctly under different data scenarios. But the test itself doesn't change from context to context, so it seems repetitive to type it all out 3 times.
What are some alternatives to DRY up this test set? Can I stick the new test in some module or does RSpec have some built in functionality to let me define it once and call it from each context block?
Thanks!
You can use shared_examples ... define them in spec/support/shared_examples.rb
shared_examples "redirect_help" do
it "clicking help redirects to the user's help page" do
click_on foo_button
expect(response).to have('bar')
end
end
Then in each of your contexts just enter...
it_behaves_like "redirect_help"
You can even pass a block to it_behaves_like and then perform that block with the action method, the block being unique to each context.
Your shared_example might look like...
shared_examples "need_sign_in" do
it "redirects to the log in" do
session[:current_user_id] = nil
action
response.should render_template 'sessions/new'
end
end
And in your context you'd call it with the block...
describe "GET index" do
it_behaves_like "need_sign_in" do
let(:action) {get :index}
end
...
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
When doing functional tests for controllers in rails how can I provide dynamic application instance variables to my test which live in the request.
I have a #station object that is initialized in a before action in my application controller (available in all other controllers), however the #station object is defined by the domain entry of the user e.g.: blue.mydomain.com. So it could have 3 different flavors, and the controller actions params[:id] are only valid for a certain flavor.
Further if I don't give my #station a flavor for my test environment it will fail utterly:
(Here code from a helper that gets called in a before action in my application_controller.rb)
def init_station
if Rails.env == "test"
#station=Station.new('blue')
else
#station=Station.new(domain_flavor_picker)
end
end
ApplicationController
....
before_action :init_station
.....
end
Thus I can only test for 'blue' or switch the flavor in my before action and then mock for different id!
test:
describe MyController do
before do
#id="10215d8da3f4f278cec747f09985b5528ec9e"
end
it "should get index action" do
p assigns(:station) # is nil
get :artist_biography, id: #id, locale: I18n.available_locales.sample
assert_response :success
assert_not_nil assigns(:meta)
assert_not_nil assigns(:nav)
assert_not_nil assigns(:content)
end
end
As you can see I am also in need of providing a locale variable. I managed to mix up that call with I18n.available_locales.sample
How can I dynamically switch or manipulate my #station instance variable?
My issue was that I needed to provide minitest with an initial host! From #smathy answer I knew that I needed a Mock Request for the Controller!
Turns out that it is quite easy to set it in MiniTest if you know how!
Rails provides an ActionDispatch::TestRequest object which in itself seems to be a Rack::MockRequest object:
DEFAULT_ENV = Rack::MockRequest.env_for('/', 'HTTP_HOST' => 'test.host', 'REMOTE_ADDR' => '0.0.0.0', 'HTTP_USER_AGENT' => 'Rails Testing' )
So all I had to do in my test was:
before do
#request.env['HTTP_HOST'] = %w(blue.mydomain.com red.mydomain.com green.mydomain.com).sample
end
to initialize my #station object with a sample of flavored domains.
assigns :station will only return a value after you do the request, ie. after the get line. Until you've done the request none of your controller code has been run for that test.
You also shouldn't use #vars in rspec, use let instead, and a few other things that I've shown below, many of which I learned from BetterSpecs
The Crux of your Issue
Assuming that domain_flavor_picker is a method in your controller then you should just mock that so you can different tests for the different return values of it. So, this shows the context for one of the return values of domain_flavor_picker, but you'd add other contexts for other values:
describe MyController do
let(:id) { "10215d8da3f4f278cec747f09985b5528ec9e" }
describe "GET /artist_biography" do
context "when domain_flavor is blue" do
before do
allow(controller).to receive(:domain_flavor_picker) { "blue" } # <-- MOCK!
end
context "when valid id" do
before { get :artist_biography, id: id, locale: I18n.available_locales.sample }
subject { response }
it { is_expected.to be_success }
it "should assign :meta" do
expect(assigns :meta).to be_present # better to have an actual matcher here
end
it "should assign :nav" do
expect(assigns :nav).to be_present # ditto
end
it "should assign :content" do
expect(assigns :content).to be_present # ditto
end
end
end
end
end
I'm an RSpec newb, but am really loving how easy it is to write the tests and I'm continually refactoring them to be cleaner as I learn new features of RSpec. So, originally, I had the following:
describe Account do
context "when new" do
let(:account) { Account.new }
subject { account }
it "should have account attributes" do
subject.account_attributes.should_not be_nil
end
end
end
I then learned about the its method, so I tried to rewrite it as such:
describe Account do
context "when new" do
let(:account) { Account.new }
subject { account }
its(:account_attributes, "should not be nil") do
should_not be_nil
end
end
end
This fails due to its not accepting 2 arguments, but removing the message works just fine. The issue is that if the test fails, the message under the Failed examples section just says
rspec ./spec/models/account_spec.rb:23 # Account when new account_attributes
which isn't overly helpful.
So, is there a way to pass a message to its, or better yet, have it output a sane message automatically?
You could define an RSpec custom matcher:
RSpec::Matchers.define :have_account_attributes do
match do |actual|
actual.account_attributes.should_not be_nil
end
failure_message_for_should do
"expected account_attributes to be present, got nil"
end
end
describe Account do
it { should have_account_attributes }
end
You can also write: its(:account_attributes) { should_not be_nil }
See https://www.relishapp.com/rspec/rspec-core/v/2-14/docs/subject/attribute-of-subject
Take note that "its" will be extracted from rspec-core to a gem with the release of rspec 3, though.
Looks like a relatively simple monkey-patch will enable what you seek.
Look at the source of the rspec-core gem version you're using. I'm on 2.10.1. In the file lib/rspec/core/subject.rb I see the its method defined.
Here's my patched version - I changed the def line and the line after that.
Caution - this is very likely to be version specific! Copy the method from your version and modify it just like I did. Note that if the rspec-core developers do a major restructuring of the code, the patch may need to be very different.
module RSpec
module Core
module Subject
module ExampleGroupMethods
# accept an optional description to append
def its(attribute, desc=nil, &block)
describe(desc ? attribute.inspect + " #{desc}" : attribute) do
example do
self.class.class_eval do
define_method(:subject) do
if defined?(#_subject)
#_subject
else
#_subject = Array === attribute ? super()[*attribute] : _nested_attribute(super(), attribute)
end
end
end
instance_eval(&block)
end
end
end
end
end
end
end
That patch can probably be put in your spec_helper.rb.
Now the usage:
its("foo", "is not nil") do
should_not be_nil
end
Output on failure:
rspec ./attrib_example_spec.rb:10 # attr example "foo" is not nil
If you omit the second arg, the behavior will be just like the unpatched method.
I have an Ruby on Rails 3 admin_controller with the default set of CRUD, index and so on methods. I'd like to test each of these for certain assertions with rspec.
Like response.should render_template("layouts/some_layout") or tests that it should require login.
Copy-pasting that test into the group of tests for each method is a lot of duplication. IMO it makes little sense to have an
it 'should require login' do
Duplicated several times troughout that test.
Is there a simple way to run a test on a list of methods? Say defined_methods.each do |method| it 'should' .... of some sort?
Is this a good way in the first place? Or am I taking a wrong route in the first place?
Given that you really want all those assertions, have you considered shared example groups?
shared_examples_for "an action that requires authentication" do
it "should render successfuly" do
sign_in(user)
response.should be_success # or whatever
end
it "should deny access" do
# don't sign_in the user
# assert access was denied
end
end
shared_examples_for "another behaviour" do
# ...
end
let(:user) { create_user }
describe "#index" do
before(:each) { get :index }
it_behaves_like "an action that requires authentication"
it_behaves_like "another behaviour"
end
describe "#show" do
before(:each) { get :show }
it_behaves_like "an action that requires authentication"
end
# ...
Of course before writing large number of specs for a basic functionality you should always check if it isn't already tested by the library that is providing the functionality (e.g. checking for the rendered template, if it is handled by rails's implicit rendering, might be a bit overkill).
If you wanted to go down the route of iteratively testing each public method in the controller, you could do something like:
SomeController.public_instance_methods(false).each do |method|
it "should do something"
end
However, I think a shared example group (see about half way down this page: http://rspec.info/documentation/) would be prettier. If it were extracted so it could be used across all your controller specs, it'll be even nicer..
shared_examples_for "admin actions" do
it "should require login"
end
Then in each controller spec:
describe SomeController do
it_should_behave_like "admin actions"
end
Just add it to your test_helper.rb, something like:
def requires_login
...
end