How can I DRY up my Rspec/Capybara suite? - ruby-on-rails

In my suite I have this in many it blocks:
let(:user) { create(:user) }
let(:plan) { Plan.first }
let(:subscription) { build(:subscription, user: user ) }
it "something" do
subscription.create_stripe_customer
subscription.update_card valid_card_data
subscription.change_plan_to plan
login_as user
end
How could I DRY this up so I don't have to duplicate all these lines across many files?

You can also create a method like
def prepare_subscription
subscription.create_stripe_customer
subscription.update_card valid_card_data
subscription.change_plan_to plan
end
And in your it block like so:
it "something" do
prepare_subscription
login_as user
end

You ain't checking value for that spec so it always green.
If you need prepare some data before test then you could put that code into helper and call it when needed in (for example) before block.
If you need check spec passing again and again then you could use shared examples.

Related

How to stub zendesk_api current_user for a spec?

I have a model method which I am trying to write a spec for. The method is like this:
def my_method
puts current_user.user_attirbute
end
Where current_user is provided by an authentication gem, zendesk_api-1.14.4. To make this method testable, I changed it to this:
def my_method(user_attribute = nil)
if user_attribute = nil
user_attribute = current_user.user_attribute
end
puts user_attribute
end
This refactor works and is testable, but doesn't seem like a good practice. Ideally the gem would provide some sort of test helper to help stub/mock the current_user, but I haven't been able to find anything. Any suggestions?
You can go simple way and just test returning of proper value by current_user#user_attribute method. Example:
describe '#my_method' do
subject { instance.my_method } # instance is an instance of your class where #my_method is defined
let(:user) { instance_spy(ZendeskAPI::User, user_attribute: attr) }
let(:attr} { 'some-value' }
before do
allow(instance).to receive(:current_user).and_return(user)
end
it { is_expected.to eq(attr) }
end
But I would go with VCR cassette(vcr gem is here) because it is related 3rd party API response - to minimize a risk of false positive result. Next example demonstrates testing with recorded response(only in case if #current_user method performs a request to zendesk):
describe '#my_method', vcr: { cassette_name: 'zendesk_current_user' } do
subject { instance.my_method }
it { is_expected.to eq(user_attribute_value) } # You can find the value of user_attribute_value in recorded cassette
end
P.S. I assumed that you put puts in your method for debugging. If it is intentional and it is part of the logic - replace eq with output in my example.

Rspec: how to create a method to call and create all mocks?

I am using Rspec to test my rails application and FactoryBot (new nome for factory girl) to create mocks. But in each file I need to call a lot of mocks. And it's almost the something, call some mocks, create an user, login and confirm this user. Can I create a method to call all this mocks, login and confirm the user? Then I just need to call this method.
Here is what I call in each rspec file:
let!(:nivel_super) { create(:nivel_super) }
let!(:gestora_fox) { create(:gestora_fox) }
let!(:user_mock) { create(:simple_super) }
let!(:super_user) { create(:super_user,
nivel_acesso_id: nivel_super.id, gestora_id: gestora_fox.id) }
let!(:ponto_venda) { create(:ponto_venda1) }
let!(:tipo_op) { create(:tipo_op) }
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
super_user.confirm
sign_in super_user
end
I'm thinking something like:
def call_mocks
# put the code above
end
Than in each rspec file:
RSpec.describe OrdemPrincipalsController, type: :controller do
describe 'Ordem Principal tests' do
call_mocks
it 'go to new page' do
# my test
end
end
end
You should simply use shared context for that
https://relishapp.com/rspec/rspec-core/v/3-4/docs/example-groups/shared-context
It is similar to shared_example but for context which is what you want
Nevertheless, you you got exactly the same setting for a lot of tests, consider putting them together in the same context
Enjoy :)

Testing interaction requests sequences

There best practices is do not use #instance variables and use let(:object). But how to write test of call sequences where each step require previous state but not clean state.
I'd like to write code like this:
describe "intearction" do
let(:user1) { ... }
let(:user2) { ... }
it "request" do
get "/api/v1/request", {user2}, token(user1)
expect(...).to ...
end
it "confirm" do
get "/api/v1/confirm", {user1}, token(user2)
expect(...).to ...
end
end
But that will not work. It may be worked only like this:
describe "intearction" do
let(:user1) { ... }
let(:user2) { ... }
it "all tests" do
# request
get "/api/v1/request", {user2}, token(user1)
expect(...).to ...
# confirm
get "/api/v1/confirm", {user1}, token(user2)
expect(...).to ...
end
end
The it become unusable and names of tests become comments or puts to console. The rspec subsystem loses its purpose. Using #instance variables is bad practice, is there any let-like definition function that does not create new plain variable per each test but per group of tests?
The sequences and api's exists not the first year. What is about your practice of testing sequences?
In tests when you need to prepare environment for the test you do it in before block. So here is how your tests might look like
describe "intearction" do
let(:user1) { ... }
let(:user2) { ... }
describe '#request' do
it "does something" do
get "/api/v1/request", {user2}, token(user1)
expect(...).to ...
end
end
describe '#confirm' do
context 'request was made before' do
before { get "/api/v1/request", {user2}, token(user1) }
it "does something as well" do
get "/api/v1/confirm", {user1}, token(user2)
expect(...).to ...
end
end
end
end

Passing a model instance to a helper method in rspec testing

I have a helper method in my app located in spec/support/utilities.rb
I am trying to pass an instance of a model object to it but I haven't succeeded so the tests fail
here is the helper method
def attribute_not_present(model_instance,model_attrib)
describe "when #{model_attrib} is not present" do
before { model_instance.send("#{model_attrib}=", " ") }
it { should_not be_valid }
end
end
in spec/model/tool_spec.rb i have this
require 'spec_helper'
describe Tool do
before do
#tool = FactoryGirl.create(:tool)
end
#attribute_array = ["kind", "serial_number", "department", "size",
"description", "hours", "length"]
subject { #tool }
#checks for absence of any of the required attributes
#attribute_array.each { |tool_attribute|
attribute_not_present(#tool,tool_attribute)
}
end
the #tool seems not to be recognized in the helper
the sample failure is this
1) Tool when size is not present
Failure/Error: before { model_instance.send("#{model_attrib}=", " ") }
NoMethodError:
undefined method `size=' for nil:NilClass
# ./spec/support/utilities.rb:3:in `block (2 levels) in attribute_not_present'
I am rails newbie
At the point where attribute_not_present is called, #tool does not yet exist. Moreover, in one case self is the example group, where when the spec is actually run (and inside your before blocks) self is an instance of the example group.
You don't need to pass model_instance through at all though - you could instead just use subject i.e.
before { subject.send("#{model_attrib}=", " ") }
however.
You may also want to look at shared examples.
Ok - I think I understand what you're trying to do here. You're trying to do unit tests, specifically validations on your Tool class, is that right?
If so, I personally like to use the shoulda_matchers gem which I find to be very idiomatic with exactly this.
As an example, you could do:
describe Tool do
it { should validate_presence_of(:kind) }
it { should validate_presnece_of(:serial_number) }
end
You can even do more with the validations, say you knew the :serial_number can only be an integer, you could do:
it { should validate_numericality_of(:serial_number).only_integer }
This is probably a better way to do unit level validations than a helper method as it's more Ruby-like.

Best practice for reusing code in Rspec?

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

Resources