When testing with rspec, where to put common "test utility methods"? - ruby-on-rails

Suppose you have a shopping site that sells widgets. However, the inventory of each widget is limited, so it's important to keep the "widget.number_still_available" number up to date.
I'd like to write an rspec test along the lines of
it "always displays the correct number still available" do
# Assume there is a before method that sets up a widget with 5 available
widget.number_still_available.should == 5
# User "a#b.com" purchases 2 widgets
widget.number_still_available.should == 3
# User "c#d.com" purchases 1 widget
widget.number_still_available.shhould == 2
# User "a#b.com" cancels purchase of 1 widget
widget.number_still_available.should == 4
end
I'd like to be able to write testing-only methods that performs the "purchasing" and "canceling" methods. These actions don't correspond to any "real" methods in my models for a variety of reasons (most significantly there is a decoupled back-end system in PHP that performs part of the purchasing and canceling actions).
Where is the correct place to put this code when using RSpec? In cucumber, I could write a couple of steps - but I'm not sure what the correct equivalent is for RSpec.

I would suggest making a new file in spec/support called purchase_helpers.rb and put this content in it:
module PurchaseHelpers
def purchase_widgets(user, count=1)
# Code goes here
end
def cancel_purchase(user, count=1)
# Code goes here
end
end
RSpec.configure do |c|
c.include PurchaseHelpers
end
The benefit of doing this rather than chucking it into spec/spec_helper.rb is that it is not crowding up that file with a lot of unrelated-to-the-setup-of-RSpec code. Separating things out is the better way to do things.

You can drop a monkeypatch into spec_helper.rb, or directly at the top of the spec file if it's only used for that one file.
It would be more clear and safe to make helper methods which use existing class methods, instead of monkeypatching the classes.

Related

How to make a custom method that can be used anywhere in rails app

I wish to make a custom method (e.g. def plus_two(x) x + 2 end and have it be accessible everywhere within the app - that is, accessible in the controller, model, console, views, tests, and any other .rb files. I currently have the same method defined in many areas of the app, and wish to make it DRY
How can this be achieved?
Note: I don't mind if calling the method requires prepending something (I have seen some answers where methods are prepended with :: or with a namespace, but otherwise have a preference to keep code succinct where possible
I have done some reading at similar questions (e.g. this one) but I can't quite get it
Reading the comments it seems like you are just looking for a clear and simple example of a method that is available everywhere in your application:
# in app/models/calculator.rb
module Calculator
def self.plus_two(x)
x + 2
end
end
Which can be called like this:
Calculator.plus_two(8)
#=> 10

Method that checks if user has a plan and then adds another plan

I'm writing a model method that does this:
Checks if the user has plan_one. If so, then deactivate plan_one.
Checks if the user has plan_two. If not, then add plan_two to the user.
Can I do this all in a single method, or do I need to set up a second method and then call it in the first method?
def have_plan_one?
# function that checks if user has a plan_one
sub.plans.active.where(name: plan_one).any?
# if user has plan 1, deactivate it
if sub.have_plan_one?
sub.plans.where(name: plan_one).deactivate
end
# if user doesn't have plan 2, add it
if sub.have_plan_two? == false
sub.plans.where(name: plan_two).add
end
end
def have_plan_two?
# function that checks if user has a plan_two
sub.plans.active.where(name: plan_two).any?
end
Decoupling your code into different methods is mostly about conceptual approaches rather then real technical difference. According to Sandy Metz rules(https://robots.thoughtbot.com/sandi-metz-rules-for-developers) I would suggest you to keep your methods up to 5 lines of code. I would go with something like this:
def switch_plans
deactivate_plan_one
activate_plan_two
end
def deactivate_plan_one
sub.plans.active.where(name: plan_one).first.try(:deactivate)
end
def activate_plan_two
sub.plan.where(name: plan_two).add if sub.have_plan_two? == false
end
You can make switch_plans just to contain 2 rows, that I wrote in separate methods, but I think this is something that definitely would be reused in other places, so I would keep it as I wrote.
Also, I want to mention, that logic inside methods still don't look good and you can think about some ways to make it better

Testing function contains an API request

I'm trying to test my rails application which using Stripe APIs, So I started with models, I'm using Rspec, The model which i want to test is called bank_account.rb inside it there is a function called (create_bank_account) with argument (bank_token) its pseudocode is something like this:
def create_bank_account(bank_token)
# make a Stripe request and save it in local variable
# save needed data in my bank_account table in my DB
end
when i started to test this function, I found that there is an API call inside it, Which is not good, I need my test not to depend on Internet, So after searching I found 'StripeMock` gem, It is useful and i started to use it with Rspec, but I found my self writing a test like this:
it 'with valid bank_token` do
# create a double for bank_account
# using StripeMock to get a faked response for creating
# new bank_account
# expect the doube to receive create_bank_account
# function and response with saving the data inside the DB
end
but after writing this I noticed that I didn't actually run create_bank_account function i faked it, So my questions are:
1- How can i test function that includes API request but run the function it self not faking it?
2- I read a lot about when we use doubles and stubs and what i understood is when a function is not completed, but if the functions is already implemented should i use doubles to avoid something like functions that call APIs?
First and foremost:
Do not create a double for bank_account.
Do not mock/stub bank_account.create_bank_account.
If you do either of these things, in a test that is supposed to be testing behaviour of BankAccount#create_bank_account, then your test is worthless.
(To prove this point, try writing broken code in the method. Your tests should obviously fail. But if you're mocking the method, everything will remain passing!!)
One way or another, you should only be mocking the stripe request, i.e. the behaviour at the boundary between your application and the internet.
I cannot provide a working code sample without a little more information, but broadly speaking you could refactor your code from this:
def create_bank_account(bank_token)
# make a Stripe request and save it in local variable
# save needed data in my bank_account table in my DB
end
To this:
def create_bank_account(bank_token)
stripe_request = make_stripe_request(bank_token)
# save needed data in my bank_account table in my DB
end
private
def make_stripe_request(bank_token)
# ...
end
...And then in your test, you can use StripeMock to only fake the response of BankAccount#make_stripe_request.
If the code is not so easy to refactor(?!), then stubbing the Stripe library directly like this might not be practical. An alternative approach you can always take is use a library like webmock to simply intercept all HTTP calls.

How can I test different app configurations in Rails?

I have an model whose behavior should change slightly based on a configuration file. The configuration file, in theory, will be altered for each installation of the app for my clients. So how can I test for these changes?
For example...
# in app/models/person.rb
before_save automatically_make_person_contributer if Rails.configuration.x.people['are_contributers_by_default']
# in test/models/person_test.rb
test "auto-assigns role if it should" do
# this next line doesn't actually work when the Person#before_save runs...
Rails.configuration.x.people['are_contributers_by_default'] = true
end
test "won't auto assign a role if it shouldn't" do
# this next line doesn't actually work when the Person#before_save runs...
Rails.configuration.x.people['are_contributers_by_default'] = false
end
It doesn't make sense for these to be stored in the database, because they are one time configurations, but I need to make sure my app behaves under all the possible configurations in all environments.
Looks like the way to make this work is to rewrite the Person class so that automatically_make_person_contributer actually performs the evaluation of Rails.configuration.x.people['are_contributers_by_default']. This makes my tests happy and technically doesn't change the way the app works:
# in app/models/person.rb
before_save :automatically_make_person_contributer
private
def automatically_make_person_contributer
if Rails.configuration.x.people['are_contributers_by_default']
# do the actual work here
end
end
However, this means that a value that is going to remain the same for the lifetime of the app's process will be checked every time a Person is created, instead of checked only once at the creation of the Person class.
In my particular case, this tradeoff is fine, but others may want the actual answer to my question.

what RSpec approach could I use for this requirement

Background: So I have roughly (Ruby on Rails app)
class A
def calculate_input_datetimes
# do stuff to calculate datetimes - then for each one identified
process_datetimes(my_datetime_start, my_datetime_end)
end
def process_datetimes(input_datetime_start, input_datetime_end)
# do stuff
end
end
So:
I want to test that calculate_input_datetimes algorithms are working
and calculating the correct datetimes to pass to process_datetimes
I know I can STUB out process_datetimes so that it's code won't be
involved in the test
QUESTION: How can I setup the rspec test however so I can specifically
test that the correct datestimes were attempted to be passed over to
process_datetimes, So for a given spec test that process_datetimes was
called three (3) times say with the following parameters passed:
2012-03-03T00:00:00+00:00, 2012-03-09T23:59:59+00:00
2012-03-10T00:00:00+00:00, 2012-03-16T23:59:59+00:00
2012-03-17T00:00:00+00:00, 2012-03-23T23:59:59+00:00
thanks
Sounds like you want should_receive and specifying what arguments are expected using with, for example
a.should_receive(:process_datetimes).with(date1,date2)
a.should_receive(:process_datetimes).with(date3,date4)
a.calculate_input_datetimes
There are more examples in the docs, for example you can use .ordered if the order of these calls is important

Resources