Understanding RSpec testing - ruby-on-rails

I've been searching around for a while, and did not find a clear explanation about testing structure in RoR. (I'm learning with Michael Hartl's book).
Since I'm using rspec for testing, do I need to keep the "test" folder anymore ?
Is the "spec" folder structure strictly assigned to specific test purposes ?
When "generating" a test script, does it do something else than creating the script file ? (i.e. could I create it by hand ?)
The 2nd question comes from this error:
undefined method `visit' for #<RSpec::Core::ExampleGroup: ...
which occurs as soon as my very simple test file is not in /spec/requests (but in /spec/views):
require 'spec_helper'
describe "Home page" do
subject { page }
###1
describe "title" do
before { visit root_path }
it { should have_selector('h1', text: 'Welcome') }
it { should have_selector('title', text: 'Home') }
end
end

1) No, you don't need the test folder. Only spec, unless you're using Test::Unit.
2) You can order spec however you want. The general suggested format is
spec/
controllers/
views/
models/
lib/ #Only if you have stuff in lib you need to test
spec_helper.rb
with names like spec/controllers/items_controller_spec.rb, and spec/models/item_spec.rb
3) I'm not sure what you mean. For RSpec, you just need to make a file that uses its syntax to test certain pieces of your Ruby code. It doesn't create a script -- you do.
For your last question about visit:
It seems like you're trying to use a Capybara method (visit). Capybara takes care of visiting your site in a browser, not RSpec.

No
See Below
No, only create the file
There is one specific folder structure you need to use in order to run Capybara acceptance tests. These are tests that have "visit" in them
Capybara 1.0
spec/requests
Capybara 2.0
spec/features

No, my understanding is that the test folder is generally used for Test::Unit stuff. You can delete or keep it; doesn't hurt to leave it.
There's a pretty specific way they want you to organize that stuff. If you have app/models/user.rb, for example, your spec should be in spec/models/user_spec.rb.
I don't know of anything else it does besides creating the test script. You can totally create those by hand, and I do all the time.

Related

Rails controller test with Rspec

I an trying to organize code by making partial html.erb files that are shared frequently(e.g. _form.html.erb)
I want to check whether my partial code works well with different models/controllers, so I am manually doing CRUD from the views.
It would be nicer to test my code automatically using Rspec but I have no idea. Can anyone give me some guidance how to test controller code with Rspec?
To test controller and views together you write feature specs and request specs .
Request specs are lower level specs where you send HTTP requests to your application and write expectations (aka assertions in TDD lingo) about the response. They are a wrapper around ActionDispatch::IntegrationTest. Request specs should be considered the replacement for controller specs, the use of which are discouraged by by the RSpec and Rails teams.
# spec/requests/products_spec.rb
require 'rails_helper'
RSpec.describe "Products", type: :request do
describe "GET /products" do
let!(:products) { FactoryBot.create_list(:product, 4) }
it "contains the product names" do
get "/products"
expect(response).to include products.first.name
expect(response).to include products.last.name
end
end
end
Feature specs are higher level specs that focus on the user story. They often serve as acceptance tests. They use a browser simulator named Capybara which emulates a user clicking his way through the application. Capybara can also run headless browsers (headless chrome, firefox, phantom js, webkit etc) and "real" browsers through selenium. The minitest equivalent is ActionDispatch::SystemTestCase but RSpec features do not wrap it (it took minitest/testunit years to catch up here).
# Gemfile
gem 'capybara'
# spec/features/products_spec.rb
require 'rails_helper'
RSpec.feature "Products" do
let!(:products) { FactoryBot.create_list(:product, 4) }
scenario "when a user views a product" do
visit '/'
click_link 'Products'
click_link products.first.name
expect(page).to have_content products.first.name
expect(page).to have_content products.first.description
end
end
This specs tests the products#index and products#show action as well as the root page and the associated views.
Both types of specs have their strengths and weaknesses. Feature tests are good for testing large swaths of the application but are heavy. Request specs are faster and its easier to replicate a specific request that causes a bug/issue but you're basically just matching HTML with regular expressions which is highly limited.
To check whether partial code works well with different models/controllers. You can add render_views in controller specs.
How to test controller code with Rspec?
Read the official doc https://relishapp.com/rspec/rspec-rails/docs/controller-specs
And this page may help: https://thoughtbot.com/blog/how-we-test-rails-applications

Rspec surrounding shared_example with timecop

I am trying to wrap Timecop around a shared_example spec i wrote
describe "timecop wrapping shared example" do
Timecop.freeze(1.day.ago) do
it_behaves_like "a shared example i would like to test using timecop"
end
end
shared_examples "a shared example i would like to test using timecop"
before :all do
puts "the current time is #{Time.now}"
end
... expect ...
end
but running this spec still uses the real time and not the frozen one
can Timecop work like this?
how else can i wrap large pieces of my test file but change the time it is running
Timecop needs to execute within an it or before block.
The following change will fix your problem.
describe "timecop wrapping shared example" do
before { Timecop.freeze(1.day.ago) }
it_behaves_like "a shared example i would like to test using timecop"
end
Rspec will reset the environment(including Timecop) after running each example.
A couple of tips I've found with time travel in tests that might be useful:
Timecop.travel is more reflective of how your code will work in real life as time never stays still. You can use it the same way as freeze, just swap out the freeze method for travel.
If you have Rails 4.1 or newer there are great time travel tools built in and you can drop your timecop gem. Here is a blog post and the docs if you want to learn more.

Rspec 3.X: native built in html matchers? Or do we have to use Capybara?

I've been reading a ton of docs and SO questions/ answers on all the changes as Rspec has evolved, want to be sure of the answer...
My goal is to use native Rspec-rails (I have 3.2.2) to do integrated controller/view tests that look for 1) CSS classes and 2) ID selectors. In other words given this view snippet:
<!-- staticpages/dashboard -->
<div class="hidden">Something</div>
<div id="creation">This</div>
This should pass (however it should be semantically written):
describe StaticpagesController do
render_views
it "should find everything" do
get :dashboard
expect(response.body).to have_selector("div#creation")
expect(response.body).to have_css("hidden")
expect(response.body).to_not have_selector("div#nothinghere")
end
end
I would like to do this without additional gems like Capybara; is that possible?
Here's a high level of what I've learned so far:
in Rspec 1, the have_tag feature allowed you to do this (http://glenngillen.com/thoughts/using-rspec-have-tag)
in Rspec 2, the have_tag was replaced with webrat's have_selector (have_tag vs. have_selector)
in Rspec 3, webrat support has been removed (http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/)
In my own experimentation, the code above generated:
Expect<long response.body here>.to respond to `has_selector?`
So that has indeed been deprecated. Still, I'd love to know if there's some other way to do this that I don't know about.
IF it turns out I need Capybara to do these fancy matchers, is there a way to do this in my integrated controller/view specs? My understanding is that I have to add type: :feature to the describe StaticpagesController line to use Capybara's matchers. However, the minute I do that, render_views is no longer available (since it's limited to type: :controller). Note, render_views also dies if, per this post (https://www.relishapp.com/rspec/rspec-rails/v/2-99/docs/controller-specs/use-of-capybara-in-controller-specs), I manually include Capybara::DSL into my controller spec. Anyway, I would really like to not have to rewrite my current controller specs into a bunch of feature specs...
It would seem that you want feature specs (with Capybara) more than controller specs as you're not testing any of the things controller specs are typically used to test such as:
whether a template is rendered
whether a redirect occurs
what instance variables are assigned in the controller to be shared with the view
the cookies sent back with the response
Also, you probably want to consider writing feature specs for new apps over controller specs since controller tests will probably be dropped in Rails 5 in favor of the writing of integration/feature tests.
For a description of the different kinds of specs that you could write, and what they're typically used for,
see this SO answer.

Capybara features outside of /features directory

I have some large end-to-end integration tests that for CI purposes I don't want in my spec/features folder in Capybara. Instead I have them in a spec/integration folder. Knowing that Capybara loads differently based on the folder, I put the :type=>:feature option on my describe, like so:
describe 'Recurring Contract Orders', :type=>:feature, :js=>true, :focus=>true do
it "satisifies the use case" do
....
But no luck, I am still getting the:
NameError:
undefined local variable or method `page' for #<RSpec::Core::ExampleGroup::Nested_1:0x007fd396bd2998>
error when I run the test. Is there something else I'm missing?
I don't know if it's something you are still trying to solve, but I was having the same problem. You are able to use the specific Capybara commands if you just include the DSL:
include Capybara::DSL
I haven't found out how to just have Capybara include my /integration folder yet, but this has worked for now.
For future ref, I think the right thing (according to https://github.com/jnicklas/capybara) is to tag the specs with :type => :feature, e.g.
describe "Some pages", :type => :feature do
specify "some behaviour"
end

Should I write rails tests with the def or test keyword?

This seems like a simple question but I can't find the answer anywhere. I've noticed that in general, tests in a Ruby on Rails app can be written as:
test "the truth" do
assert true
end
or
def the_truth
assert true
end
It seems newer material writes tests the first way, but I can't seem to find a reason for this. Is one favored over the other? Is one more correct? Thanks.
There has been a shift in recent years from short, abbreviated test names to longer, sentence-like test names. This is partly due to the popularity of RSpec and the concept that tests are specs and should be descriptive.
If you prefer descriptive test names, I highly recommend going with the test method. I find it to be more readable.
test "should not be able to login with invalid password" do
#...
end
def_should_not_be_able_to_login_with_invalid_password
#...
end
Also, because the description is a string it can contain any characters. With def you are limited in which characters you can use.
I believe the first method was implemented starting with Rails 2.2.
As far as I am aware, it simply improves readability of your code (as def can be any function while test is used only in test cases).
Good luck!
As Mike Trpcic suggests you should check out RSpec and Cucumber. I'd like to add that you should also take a look at:
Shoulda (http://github.com/thoughtbot/shoulda/tree/master)
Factory Girl (http://github.com/thoughtbot/factory_girl/tree/master)
Shoulda is a macro framework for writing concise unit tests for your models/controllers, while the second is a replacement for fixtures.
I would suggest doing your testing with either RSpec or Cucumber. I use both to test all my applications. RSpec is used to test the models and controllers, and Cucumber tests the Views (via the included Webrat functionality).

Resources