I'm trying to split my code in RSpec into multiple files so it looks nicer. The current file looks like this.
require 'rails_helper'
RSpec.describe Api::MyController do
let(:var1) {}
let(:var2) {}
it 'should calculate some value' do
expect(var1 + var2).to eq('some value')
end
end
Now this is how it looks after refactoring.
require 'rails_helper'
require_relative './mycontroller/calculation'
RSpec.describe Api::MyController do
let(:var1) {}
let(:var2) {}
include Api::MyController::Calculation
end
And this is how calculation.rb looks like.
module Api::MyController::Calculation
it 'should calculate some value' do
expect(var1 + var2).to eq('some value')
end
end
The problem now is that when it runs, it complains var1 and var2 is not defined.
I believe you are looking for RSpec's shared examples:
# spec/support/shared_examples/a_calculator.rb
RSpec.shared_examples "a calculator" do
it 'should calculate some value' do
expect(x+y).to eq(result)
end
end
You then include the shared example with any of:
include_examples "name" # include the examples in the current context
it_behaves_like "name" # include the examples in a nested context
it_should_behave_like "name" # include the examples in a nested context
matching metadata # include the examples in the current context
You can pass context to the shared example by passing a block:
require 'rails_helper'
require 'support/shared_examples/a_calculator'
RSpec.describe Api::MyController do
it_should_behave_like "a calculator" do
let(:x){ 1 }
let(:y){ 2 }
let(:result){ 3 }
end
end
Related
Is it possible to do something like this?
module MyHelper
before (:each) do
allow(Class).to receive(:method).and_return(true)
end
end
Then in my tests I could do something like:
RSpec.describe 'My cool test' do
include MyHelper
it 'Tests a Class Method' do
expect { Class.method }.to eq true
end
end
EDIT: This produces the following error:
undefined method `before' for MyHelper:Module (NoMethodError)
Essentially I have a case where many tests do different things, but a common model across off of them reacts on an after_commit which ends up always calling a method which talks to an API. I dont want to GLOBALLY allow Class to receive :method as, sometimes, I need to define it myself for special cases... but I'd like to not have to repeat my allow/receive/and_return and instead wrap it in a common helper...
You can create a hook that is triggered via metadata, for example :type => :api:
RSpec.configure do |c|
c.before(:each, :type => :api) do
allow(Class).to receive(:method).and_return(true)
end
end
And in your spec:
RSpec.describe 'My cool test', :type => :api do
it 'Tests a Class Method' do
expect { Class.method }.to eq true
end
end
You can also pass :type => :api to individual it blocks.
It is possible to do things like you want with feature called shared_context
You could create the shared file with code like this
shared_file.rb
shared_context "stubbing :method on Class" do
before { allow(Class).to receive(:method).and_return(true) }
end
Then you could include that context in the files you needed in the blocks you wanted like so
your_spec_file.rb
require 'rails_helper'
require 'shared_file'
RSpec.describe 'My cool test' do
include_context "stubbing :method on Class"
it 'Tests a Class Method' do
expect { Class.method }.to eq true
end
end
And it will be more naturally for RSpec than your included/extended module helpers. It would be "RSpec way" let's say.
You could separate that code into shared_context and include it into example groups (not examples) like this:
RSpec.describe 'My cool test' do
shared_context 'class stub' do
before (:each) do
allow(Class).to receive(:method).and_return(true)
end
end
describe "here I am using it" do
include_context 'class stub'
it 'Tests a Class Method' do
expect { Class.method }.to eq true
end
end
describe "here I am not" do
it 'Tests a Class Method' do
expect { Class.method }.not_to eq true
end
end
end
Shared context can contain let, helper functions & everything you need except examples.
https://www.relishapp.com/rspec/rspec-core/docs/example-groups/shared-context
I would like to add a module that includes a method to help me log in as a user. Here it is:
module TestHelper
require 'spec_helper'
ALL_USERS = ["administrator", "instructor", "regular_user"]
def self.login_as(user_type)
user = User.find_by(global_role_id: GlobalRole.send(user_type))
#request.env["devise.mapping"] = Devise.mappings[:user]
sign_in user
end
end
The spec that's calling it is
require 'spec_helper'
RSpec.describe QuestionsController, :type => :controller do
include Devise::TestHelpers
include TestHelper
describe "a test" do
it "works!" do
TestHelper.login_as("administrator")
end
end
end
And here is the spec_helper
RSpec.configure do |config|
config.include TestHelper, :type => :controller
The error I get is: undefined method 'env' for nil:NilClass It looks like my module doesn't have access to #request.
My question is: how do I access and #request in the external Module?
In addition to the other answers, you should consider only including this module for relevant spec types using the following code:
config.include TestHelper, type: :controller # Or whatever spec type(s) you're using
You could pass #request in to TestHelper.login_as from the tests. Eg
module TestHelper
def self.login_as(user_type, request)
...
request.env['devise.mapping'] = Devise.mappings[:user]
...
end
end
...
describe 'log in' do
it 'works!' do
TestHelper.login_as('administrator', #request)
end
end
But probably better to follow the macro pattern from the devise wiki if other devise mappings in play.
Every model I'm testing has the same "it must have atribute" test, testing the validates_presence_of for certain attributes. So, my goal is to create a "helper" that includes this test in a modular way.
Here is what I have:
# /spec/helpers.rb
module TestHelpers
# Runs generic validates_presence_of tests for models
def validate_presence( object, attributes=[] )
attributes.each do |attr|
it "must have a #{attr}" do
object.send("#{attr}=", nil)
expect(object).not_to be_valid
end
end
end
end
And
# /spec/rails_helper.rb
# Added
require 'helpers'
# Added
RSpec.configure do |config|
config.include TestHelpers
end
And
# /spec/models/business_spec.rb
require 'rails_helper'
RSpec.describe Business, type: :model do
describe "Validations" do
before :each do
#business = FactoryGirl.build(:business)
end
# Presence
validate_presence #business, %w(name primary_color secondary_color)
However, I'm getting the following error:
`validate_presence` is not available on an example group
I had read about shared_helpers and using it_behaves_as, but I'm not sure if that is what I'm looking for. Maybe I'm just thinking about doing this in the wrong manner.
--UPDATE--
If I place the validate_presence method into an it block, I get this error:
Failure/Error: it { validate_presence #business, %w(name primary_color secondary_color published) }
`it` is not available from within an example (e.g. an `it` block) or from constructs that run in the scope of an example (e.g. `before`, `let`, etc). It is only available on an example group (e.g. a `describe` or `context` block).
Shared examples are for testing the same logic across different models. Here you're just testing one model so it wouldn't apply to you. Even though I don't recommend testing core validators such as presence, here is how you would do it
# /spec/models/business_spec.rb
require 'rails_helper'
RSpec.describe Business, type: :model do
let(:business) { FactoryGirl.build(:business) }
context "when validating" do
%w(name primary_color secondary_color).each |attribute|
it "checks the presence of #{attribute} value" do
business.send("#{attribute}=", nil)
expect(business).to_not be_valid
expect(business.errors[attribute]).to be_any
end
end
end
end
Also, the validate_presence helper you're trying to use is part of the shoulda-matchers library.
I am new to ruby/rails/rspec etc.
Using rspec 2.13.1, I want to create a module with a method that can be called from my tests resulting to subsequent calls of the "it" method of the RSpec::Core::ExampleGroup.
My module:
require 'spec_helper'
module TestHelper
def invalid_without(symbols)
symbols = symbols.is_a?(Array) ? symbols : [symbols]
symbols.each do |symbol|
it "should not be valid without #{symbol.to_s.humanize}" do
# Gonna nullify the subject's 'symbol' attribute here
# and expect to have error on it
end
end
end
end
The code above was added to:
spec/support/test_helper.rb
and in my spec_helper.rb, in the RSpec.configure block, I added the following:
config.include TestHelper
Now, in a test, I do the following:
describe Foo
context "when invalid" do
invalid_without [:name, :surname]
end
end
Running this, I get:
undefined method `invalid_without' for #<Class:0x007fdaf1821030> (NoMethodError)
Any help appreciated..
Use shared example group.
shared_examples_for "a valid array" do |symbols|
symbols = symbols.is_a?(Array) ? symbols : [symbols]
symbols.each do |symbol|
it "should not be valid without #{symbol.to_s.humanize}" do
# Gonna nullify the subject's 'symbol' attribute here
# and expect to have error on it
end
end
end
describe Foo do
it_should_behave_like "a valid array", [:name, :surname]
end
I've got the view helper method in my application helper:
module ApplicationHelper
def formatted_something(something)
"Hello, #{something}"
end
end
I want to access that method in my request spec:
require "spec_helper"
describe "something" do
include RequestSpecHelper
it "should display blogs list" do
visit something_url
page.should have_content formatted_something(#something.something)
end
end
It couldn't find formatted_something method.
You just need to include the relevant helper module in your describe block, and it will be available in all nested specs:
describe "something" do
include RequestSpecHelper
include ApplicationHelper
...
end