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
Related
I'm working on a test for my Rails 4 app and I'm pretty new to using RSpec. I have a controller named AppsController which has the standard index, new, show, create... methods and they all work the way Rails suggest Etc. "new" creates a new instance of the object and create actually saves it, show, shows it and index shows all of the object. Here are my current tests can anyone see any potential problems or things that i could improve?
FactoryGirl.define do
factory :developer do
email 'example#me.com'
password 'new_york'
password_confirmation 'new_york'
tos '1'
end
factory :app do
name 'New App'
tos '1'
end
factory :invalid_app, parent: :app do
name 'nil'
tos '0'
end
end
require 'spec_helper'
def create_valid!
post :create, app: app_attributes
end
def create_invalid!
post :create, app: app_invalid_attributes
end
def show!
get :show, id: app
end
def update_valid!
put :update, id: app, app: app_attributes
end
def update_invalid!
put :update, id: app, app: app_invalid_attributes
end
def delete!
delete :destroy, id: app
end
def http_success
expect(response).to be_success
end
def expect_template(view)
expect(response).to render_template(view)
end
describe AppsController do
render_views
before(:each) do
#developer = FactoryGirl.create(:developer)
#developer.confirm!
sign_in #developer
end
let(:app) { FactoryGirl.create(:app, developer: #developer) }
let(:app_attributes) { FactoryGirl.attributes_for(:app) }
let(:app_invalid_attributes) { FactoryGirl.attributes_for(:invalid_app) }
describe 'GET #index' do
it 'responds with an HTTP 200 status' do
get :index
http_success
end
it 'renders the :index view' do
get :index
expect_template(:index)
end
it 'populates #apps with the current_developers apps' do
app = FactoryGirl.create(:app, :developer => #developer)
get :index
expect(assigns(:app)).to eq([app])
end
end
describe 'POST #create' do
context 'with valid parameters' do
it 'creates a new app' do
expect { create_valid!
}.to change(App, :count).by(1)
end
it 'redirects to the new app keys' do
create_valid!
expect(response).to redirect_to keys_app_path(App.last)
end
end
context 'with invalid parameters' do
it 'does not create the new app' do
expect { create_invalid!
}.to_not change(App, :count)
end
it 'renders the :new view' do
create_invalid!
expect_template(:new)
end
end
end
describe 'GET #show' do
it 'responds with an HTTP 200 status' do
show!
http_success
end
it 'renders the :show view' do
show!
expect_template(:show)
end
it 'populates #app with the requested app' do
show!
expect(assigns(:app)).to eq(app)
end
end
describe 'PUT #update' do
context 'with valid parameters' do
it 'locates the requested app' do
update_valid!
expect(assigns(:app)).to eq(app)
end
it 'changes app attributes' do
update_valid!
expect(app.name).to eq('Updated App')
end
it 'redirects to the updated app' do
update_valid!
expect(response).to redirect_to app
end
end
context 'with invalid parameters' do
it 'locates the requested app' do
update_invalid!
expect(assigns(:app)).to eq(app)
end
it 'does not change app attributes' do
update_invalid!
expect(app.name).to_not eq('Updated App')
end
it 'renders the :edit view' do
update_invalid!
expect_template(:edit)
end
end
end
describe 'DELETE #destroy' do
it 'deletes the app' do
expect { delete!
}.to change(App, :count).by(-1)
end
it 'redirects to apps#index' do
delete!
expect(response).to redirect_to apps_url
end
end
end
count should have been changed by -1, but was changed by 0 - on DELETE #destroy
expecting <"new"> but rendering with <[]> - on POST #create
expected: "Updated App"
got: "New App" - on PUT #update
expecting <"edit"> but rendering with <[]> - on PUT #update
expected: [#<App id: nil, unique_id: "rOIc5p", developer_id: 18, name: "New App">]
got: nil - on GET #index
Small thing - your #http_success method is testing the exact same thing twice.
You could also factor out the references to app by putting a #let statement right after your #before block:
let(:app) { FactoryGirl.create(:app, developer: #developer) }
then in your specs, just
it 'renders the :show view' do
get :show, id: app
expect_template(:show)
end
Edit:
Then the order of operations will be 1) the #developer is created in the #before block, 2) the spec is entered, 3) at the first reference to app in the spec, the #let block will create an instance of an app.
That means you can't factor out the app creation in the #index spec, because in that case the spec will call the action before it creates the app.
A few things I thought of, reading your code:
You don't need to include parens on method calls taking no arguments. Just http_success will work.
You should get try to use the modern RSpec expectation syntax consistently. Instead of assigns(:app).should eq(app), use expect(assigns(:app)).to eq(app). (There's one exception to this, which is expectations on mocks (ie. should_receive(:message)), which will only take on the modern expect-to syntax as of RSpec 3.
For controller specs, I like to create little methods for each action that actually invokes the action. You'll notice you call get :show, id: app several times in the GET #show specs. To DRY up your specs a little more, you could instead write the following method within the describe block:
def show!
get :show, id: app
end
Try to use one Hash syntax consistently. What's more, Rails 4 can't be run with Ruby 1.8, so there's (nearly) no reason to use the hash-rocket Hash syntax.
If I'm getting really, really picky, I usually consider instance variables in a spec to be a smell. In almost all cases, instance variables should be refactored to a memoized let/given blocks.
If I'm getting really, really, really picky, I prefer to think of controller specs such as yours as strictly a unit test of the controller, not an integration test (that's what Capybara is for), and as such you shouldn't be exercising your model layer at all. You should only be testing that your controller is sending the right messages to the model layer. In other words, all the model layer stuff should be stubbed out. For example:
describe 'GET #show' do
let(:app) { stub(:app) }
before do
App.stub(:find).and_return(app)
end
it 'populates #app' do
get :show, id: app
assigns(:app).should eq(app)
end
end
I know this last is a personal preference, not a metaphysical truth or even necessarily a wide-spread standard convention, so you may choose to take it or leave it. I prefer it, because it keeps my specs very speedy, and gives me a very clear heuristic for when my controller actions are doing too much, and I might need to consider refactoring. It could be a good habit to get into.
First, I'm not certain but I suspect your invalid app factory may be wrong. Did you mean
factory :invalid_app, parent: :app do
name nil
tos '0'
end
nil as a ruby NilClass not "nil" as a string?
As for other comments about cleanup and stuff, here are a few of my thoughts.
You can avoid the need for some of your helper methods and duplication by using before blocks for each describe. Taking just your index tests you could have something more like
describe 'GET #index' do
before do
get :index
end
it 'responds with an HTTP 200 status' do
http_success
end
it 'renders the :index view' do
expect_template(:index)
end
it 'populates #apps with the current_developers apps' do
expect(assigns(:app)).to eq([app])
end
end
Notice also that you don't need to recreate app because your let is doing it as necessary.
On the failures, I suspect the delete count change could be failing because inside the expectation, the test framework is creating a new app (from the let) and then deleting it leading to a count change of 0. For that test, you need to make sure you're app is created outside of your expectation. Because you are using let, you could do that like this:
describe 'DELETE #destroy' do
it 'deletes the app' do
# ensure that app is already created
app
expect {
delete!
}.to change(App, :count).by(-1)
end
end
alternatively, change the let to a let! which will force the creation before the specs actually run.
As for other failures, thought #DanielWright suggested the helper methods, I find those complicate the debug. I can't see where you set the app name to "Updated App", for example. Perhaps a clearer test (for that particular one) would not use the helper methods but could be more explicit. Something like
describe 'PUT #update' do
let(:app_attributes) { FactoryGirl.attributes_for(:app, name: 'The New App Name') }
before do
put :update, id: app, app: app_attributes
end
context 'with valid parameters' do
it 'locates the requested app' do
expect(assigns(:app)).to eq(app)
end
it 'changes app attributes' do
# notice the reload which will make sure you refetch this from the db
expect(app.reload.name).to eq('The New App Name')
end
it 'redirects to the updated app' do
expect(response).to redirect_to app
end
end
end
For the other errors, you might want to start debugging your code. Are you certain it should work? Have you looked at output logs? Maybe the tests are doing there job and finding errors in your controller code. Have you done any step-through debugging?
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
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'm currently struggling a bit trying to keep my controller specs DRY and succinct and down to one assertion per example. I'm running into some difficulties particularly with where to place the actual controller request call within a structure nested to match the various edge cases.
Here's an example, simplified to demonstrate the problem:
describe MyController do
let(:item) { Factory(:item) }
subject { response }
describe "GET #show" do
before(:each) do
get :show
end
context "published item" do
it { should redirect_to(success_url) }
end
context "unpublished item" do
before(:each) do
item.update_attribute(published: false)
end
it { should redirect_to(error_url) }
end
end
end
Clearly this is a contrived example, but it illustrates what I'd like to do and what's not working. Mainly, the before block under the "unpublished" context is the problem. What happens is the change I made to the setup data actually happens after the get call due to the way the contexts are nested, so the example in that context is actually working with the initial scenario rather than the one I intend.
I understand why this happens and how contexts nest. I guess what I'd like to have is some way to tell RSpec what I'd like it to run right after any before hooks yet right before any assertions within a given context. This would be perfect for controller specs. I'd like to take advantage of nesting in my controller specs to gradually build up variations of edge cases without having to scatter the get call or even a call to a do_get helper into each of my it assertions. This would especially get annoying to keep in sync with any custom it_should macros I'm using.
Is there anything in RSpec currently to accomplish this? Are there any tricks I can use to get close? It would seem perfectly suited to the way I've seen a lot of people writing their controller specs; from what I've found, people have basically settled for having do_get helpers called before every assertion. Is there a better way?
The DRY principle states that "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system." What you're doing is much more about saving a few characters here and there than keeping things DRY, and the result is a tangled web of dependencies up and down a hierarchy that, as you can see, is a bitch to get to do what you want it to and consequently fragile and brittle.
Let's start with what you've got written out in a way that's verbose and works:
describe MyController do
describe "GET #show" do
context "published item" do
it "redirects to the success url" do
item = Factory(:item, published: true)
get :show, :id => item.id
response.should redirect_to success_url
end
end
context "unpublished item" do
it "redirects to the error url" do
item = Factory(:item, published: false)
get :show, :id => item.id
response.should redirect_to error_url
end
end
end
end
Now the only "pieces of knowledge" that are being duplicated are the names of the examples, which could be generated by the matchers at the end of each example. This can be resolved in a readable way by using the example method, which is an alias of it:
describe MyController do
describe "GET #show" do
context "published item" do
example do
item = Factory(:item, published: true)
get :show, :id => item.id
response.should redirect_to success_url
end
end
context "unpublished item" do
example do
item = Factory(:item, published: false)
get :show, :id => item.id
response.should redirect_to error_url
end
end
end
end
There. DRY. And quite readable and easy to change. Now, when you happen to add more examples for either of the contexts, you can add a let:
describe MyController do
describe "GET #show" do
context "published item" do
let(:item) { Factory(:item, published: true) }
example do
get :show, :id => item.id
response.should redirect_to success_url
end
example do
# other example
end
end
# ...
end
end
Now the only duplicated code (not the same as the DRY principle) is the get. If you really feel strongly about it, you can delegate those calls out to a method like get_show(id) or some such, but it's not really buying much at that point. It's not like the API for get is going to change from under you, and the only argument to get is the item's id, which you actually care about in the example (so there's no unnecessary information).
As for using subject to capture the response and get one-liners out of the deal, that just makes things really difficult to read and doesn't save you much. In fact, I've come to consider using subject in this way to be a smell.
Hope this all helps.
Cheers,
David
Will
context "unpublished item" do
let(:item) do
Factory(:item, published: false)
end
it { should redirect_to(error_url) }
end
work for you? BTW, before by default is before(:each) so you can DRY you specs a little more.
UPDATE:
you can also isolate examples with anonymous contexts, like:
describe "GET #show" do
let(:show!) do
get :show
end
context do
before { show! }
context "published item" do
it { should redirect_to(success_url) }
end
# another examples with show-before-each
end
context "unpublished item" do
before do
item.update_attribute(published: false)
show!
end
it { should redirect_to(error_url) }
end
end
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