Is there any difference between a feature spec and a view spec? - ruby-on-rails

I had a question about within dryness in Capybara here. Tom answered perfectly and in his answer he mentioned:
Feature tests should be for testing larger behaviours in the system.
Is there a difference between a feature spec and a view spec in Ruby on Rails? If possible explain it with some example please.
Thank you.

Yes, feature and view specs are quite different. The first is a full integration test and the second tests a view in isolation.
A feature spec uses a headless browser to test the entire system from the outside just like a user uses it. It exercises code, database, views and Javascript too, if you use the right headless browser and turn on Javascript.
Unlike other types of rspec-rails spec, feature specs are defined with the feature and scenario methods.
Feature specs, and only feature specs, use all of Capybara's functionality, including visit, methods like fill_in and click_button, and matchers like have_text.
There are plenty of examples in the rspec-rails documentation for feature specs. Here's a quick one:
feature "Questions" do
scenario "User posts a question" do
visit "/questions/ask"
fill_in "title" with "Is there any difference between a feature spec and a view spec?"
fill_in "question" with "I had a question ..."
click_button "Post Your Question"
expect(page).to have_text "Is there any difference between a feature spec and a view spec?"
expect(page).to have_text "I had a question"
end
end
A view spec just renders a view in isolation, with template variables provided by the test rather than by controllers.
Like other types of rspec-rails spec, view specs are defined with the describe and it methods. One assigns template variables with assign, renders the view with render and gets the results with rendered.
The only Capybara functionality used in view specs is the matchers, like have_text.
There are plenty of examples in the rspec-rails documentation of view specs. Here's a quick one:
describe "questions/show" do
it "displays the question" do
assign :title, "Is there any difference between a feature spec and a view spec?"
assign :question, "I had a question"
render
expect(rendered).to match /Is there any difference between a feature spec and a view spec\?/
expect(rendered).to match /I had a question/
end
end

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 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.

Faking instance variable in RSpec and Capybara feature spec

I'm trying to set up some feature specs before I get into refactoring some of my company's old code. It's kind of an unconventional setup, but I was able to figure out enough about test doubles to bypass the authentication enough to get started. One problem I'm still having is that some of the instance variables set in these methods I'm bypassing are expected by the view, so I get undefined method for nil:NilClass errors. I would like to get the specs running before I make any changes to the program code. In this case, I could easily just move the particular instance variable to another method. But I'm sure more situations like this will come up. Here's the example I'm currently working on:
def security_level
#right_now = Time.now
#
# other code that wont work without
# connecting to a remote authentication
# server
#
end
Then in my spec:
feature 'Navigation' do
before(:each) do
allow_any_instance_of(ApplicationController).to receive(:security_level).and_return(nil)
end
scenario 'is possible' do
visit root_path
expect(page.has_content?('Quick Stats'))
end
end
Here's the error, coming from #right_now.year in the view
Failure/Error: visit root_path
NoMethodError:
undefined method `year' for nil:NilClass
# ./common/views/layouts/bootstrap/layout.haml:63
EDIT: Is there a way to set instance variables on the controller from within a feature spec?
There's no easy way to accomplish what you want.
The feature spec is handled mostly by Capybara, not RSpec. Capybara runs the majority of the browser / rails server behavior in an external process. This make it inaccessible from RSpec's point-of-view. Thus you cannot use stubs / doubles in this manner.
Feature specs are largely meant to be end-to-end acceptance tests. The idea is to exercise your system as those who would use your system do. Generally, in these types of specs you perform various "workflows". This means, having the spec, log a user in, navigate to particular pages, filling forms, clicking buttons and links. You then generally make your expectations on what you see in the view.
This means your spec would look more like:
feature 'Navigation' do
let(:regular_user) { User.create!(name: 'A Regular User') }
def sign_in(a_user)
visit sign_in_url
# fill out form
click_button 'Sign In'
end
before(:each) do
sign_in(regular_user)
end
scenario 'is possible' do
visit root_path
expect(page.has_content?('Quick Stats'))
end
end
https://github.com/per-garden/fakeldap may provide enough ldap functionality for your feature tests.

Capybara issue inside a within block

require "spec_helper"
require "rails_helper"
include Capybara::RSpecMatchers
include Capybara::DSL
Capybara.javascript_driver = :webkit
feature "Course", :type => :feature do
scenario "Get index and search for course types", js: true do
visit "/courses"
within("//body") do
find(:xpath, "//input[#id='course_type_id_1']").click
find(:xpath, "//div[#class='course-right-sec']")
expect(page).to have_content('65,171 courses')
expect(page).to have_content('Fundamentals of Design')
end
end
end
The problem i am facing with the above code is that when i find a specific div inside a within block, whether the id of the div is correct or not it passes the test.
Whoa, this is a lot of expectations for one test. While some testing purists would say one expectation per test, feature tests that need to load js are time consuming, but you might be doing too much here. One logical way would be to have one feature test for each of your courses; that way you have a couple expectations for each.
This isn't the exact solution to your problem, but it will help debugging in the future.
All of your expectations are based on the have_content matcher. Basically, if it's in the DOM on load, all of these expectations are going to pass, regardless of your find and click events. Ie. find(id).click
I might be able to help more if you give more context to whats going on here and what you are trying to test for and against. Attaching your js would help also.

Confusion about Cucumber Page Object

I've just started using cucumber to test my Rails apps. I've been very successful blindly following the many good examples.
Given /^I visit (.*) web page$/ do |page|
visit page
page.should have_text("some text")
end
Obviously, the call to visit populates the page object. And I have surmised that multiple calls to visit, or click_link, will re-fill the page object. But I'd like a better idea of where and when the page object is instantiated and its scope. Is it global or do I have to set #page= page after I call visit?
I've looked through the capybara source too and really don't have a good feel for the page object. Where can I find good documentation?
Edit: Even more confusion
It appears that I should be using have_content instead of have_text. My confusion today is that:
page.should have_content("this text does not exist on the page")
always passes. I don't understand why this does not fail?
My issue has been solved. Capybara fails silently if you are using ruby 1.8.7 (which is what I am using on my mac)
https://groups.google.com/forum/?fromgroups#!topic/cukes/B3UbbyG5k6s

Resources