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 :)
Related
I am setting up RSpec request tests, and I have the following test:
require 'rails_helper'
RSpec.describe "ClientApi::V1::ClientContexts", type: :request do
describe "POST /client_api/v1/client_contexts" do
let(:client_context) { build :client_context }
it "creates a new context" do
post "/client_api/v1/client_contexts", params: {
browser_type: client_context.browser_type,
browser_version: client_context.browser_version,
operating_system: client_context.operating_system,
operating_system_version: client_context.operating_system_version
}
expect(response).to have_http_status(200)
expect(json.keys).to contain_exactly("browser_type", "browser_version", "operating_system", "operating_system_version")
# and so on ...
end
end
end
The corresponding factory is this:
FactoryBot.define do
factory :client_context do
browser_type { "Browser type" }
browser_version { "10.12.14-blah" }
operating_system { "Operating system" }
operating_system_version { "14.16.18-random" }
end
end
Now, obviously, that all seems a bit redundant. I have now three places in which I specify the attributes to be sent. If I ever want to add an attribute, I have to do it in all of these places. What I actually want to do is send the particular attributes that the Factory specifies via POST, and then check that they get returned as well.
Is there any way for me to access the attributes (and only these!) that I defined in the Factory, and re-use them throughout the spec?
I should prefix this with a warning that abstracting away the actual parameters from the request being made could be seen as detrimental to the overall test expressiveness. After all, now you'd have to look into the Factory to see which parameters are sent to the server.
You can simply get the Factory-defined attributes with attributes_for:
attributes_for :client_context
If you need more flexibility, you can implement a custom strategy that returns an attribute Hash from your Factory without creating the object, just building it.
Create a file spec/support/attribute_hash_strategy.rb:
class AttributeHashStrategy
def initialize
#strategy = FactoryBot.strategy_by_name(:build).new
end
delegate :association, to: :#strategy
def result(evaluation)
evaluation.hash
end
end
Here, the important part is evaluation.hash, which returns the created object as a Ruby Hash.
Now, in your rails_helper.rb, at the top:
require 'support/attribute_hash_strategy'
And below, in the config block, specify:
# this should already be there:
config.include FactoryBot::Syntax::Methods
# add this:
FactoryBot.register_strategy(:attribute_hash, AttributeHashStrategy)
Now, in the Spec, you can build the Hash like so:
require 'rails_helper'
RSpec.describe "ClientApi::V1::ClientContexts", type: :request do
describe "POST /client_api/v1/client_contexts" do
let(:client_context) { attribute_hash :client_context }
it "creates a new context" do
client = create :client
post "/client_api/v1/client_contexts",
params: client_context
expect(response).to have_http_status(200)
end
end
end
The attribute_hash method will be a simple Hash that you can pass as request parameters.
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.
I am trying to set local variable in view helper through rspec. My routine is as follow
def time_format(time)
now = Time.now.in_time_zone(current_user.timezone)
end
My spec file is as follow:
it 'return 12 hour format' do
user = User.first
assign(:current_user, user)
expect(helper.time_format(900)).to eq('9:00 AM')
end
Spec is failing throwing me error undefined local variable or method 'current_user'
'current_user' resided in application_controller
Problem
Your current_user method is not available in your rspec test. That's why you are getting the mentioned error.
Solution
You either can implement current_user method inside a test helper and then use that in your test, or you can stub current_user in your spec test like this:
let(:user) { User.new }
# RSpec version >= 3 syntax:
before { allow(controller).to receive(:current_user) { user } }
before { allow(view).to receive(:current_user) { user } }
# RSpec version <= 2 syntax:
before { controller.stub(:current_user) { user } }
before { view.stub(:current_user) { user } }
This should fix your problem.
Also, if you are using devise for authentication, then I would recommend you to take a look at: How To: Test controllers with Rails 3 and 4 (and RSpec).
The following was modified from the RSpec-core 3.10 docs.
Create a new setting for RSpec.configure called my_variable, and give it a value, like this:
# spec/spec_helper.rb
RSpec.configure do |config|
config.add_setting :my_variable
config.my_variable = "Value of my_variable"
end
Access settings like class variables in RSpec.configuration from your test:
# spec/my_spec.rb
RSpec.describe(MyModule) do
it "creates an instance of something" do
my_instance = MyModule::MyClass.new(RSpec.configuration.my_variable)
end
end
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.
I'm attempting to test that my service is calling Anemone.crawl correctly. I have the following code:
spider_service.rb
class SpiderService < BaseService
require 'anemone'
attr_accessor :url
def initialize(url)
self.url = url
end
def crawl_site
Anemone.crawl(url) do |anemone|
end
end
end
spider_service_spec.rb
require 'spec_helper'
require 'anemone'
describe SpiderService do
describe "initialize" do
let(:url) { mock("url") }
subject { SpiderService.new(url) }
it "should store the url in an instance variable" do
subject.url.should == url
end
end
describe "#crawl_site" do
let(:spider_service) { mock("spider service") }
let(:url) { mock("url") }
before do
SpiderService.stub(:new).and_return(spider_service)
spider_service.stub(:crawl_site)
Anemone.stub(:crawl).with(url)
end
subject { spider_service.crawl_site }
it "should call Anemone.crawl with the url" do
Anemone.should_receive(:crawl).with(url)
subject
end
end
end
And here's the error that I'm getting, and can't understand, since I can call the service in the Rails console and I get back data from Anemone when I provide a valid URL:
Failures:
1) SpiderService#crawl_site should call Anemone.crawl with the url
Failure/Error: Anemone.should_receive(:crawl).with(url)
(Anemone).crawl(#<RSpec::Mocks::Mock:0x82bdd454 #name="url">)
expected: 1 time
received: 0 times
# ./spec/services/spider_service_spec.rb:28
Please tell me I've forgotten something silly (I can blame lack of coffee then, instead of general incompetence!)
Thank you for your time,
Gav
Your subject calls a method on the mock object that you're created (mock("spider_service")), not a real SpiderService object. You've also stubbed the call on the mock spider service to do nothing, so calling it in the subject will do nothing, hence why your test fails.
Also, you've stubbed new (although you never call it) on SpiderService to return a mock object. When you're testing SpiderService you'll want to have real instances of the class otherwise method calls will not behave as they would on a real instance of the class.
The following should achieve what you want:
describe "#crawl_site" do
let(:spider_service) { SpiderService.new(url) }
let(:url) { mock("url") }
before do
Anemone.stub(:crawl).with(url)
end
subject { spider_service.crawl_site }
it "should call Anemone.crawl with the url" do
Anemone.should_receive(:crawl).with(url)
subject
end
end
You might also want to move the require 'anenome' outside of the class definition so it is available elsewhere.