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.
Related
I'm having a weird issue where I'm testing a controller that has a procedure that uses caching. The test is failing, but if I do a binding.pry inside the method that does the caching, the test passes.
example of the method containing the caching and the binding.pry:
def method_example:
data = Rails.cache.fetch(cache_key) do
ProcedureService.new(params).generate
end
binding.pry
data
end
Example of the test:
it "reverts record amount" do
expect(record.amount).to eq((original_amount + other_amount).to_d)
end
The caching is done via redis_store.
When done in the development environment, it works fine. What I don't understand is why it is failing but passing when adding a stopper? It seems it could be something about the time it takes to fetch the cache
UPDATE
Using sleep, instead of binding.pry also makes the test pass, so I can assume this is a timing issue. What is the problem exactly? and how could I manage it?
I think this has to do with cashing enabled or not enabled in your tests:
you can set expectations like this with the current implementation in your example method:
expect(Rails).to receive_massage_change(:cashe, :fetch).and_return(expected_value)
you can also inject the ProcedureService instance to the method and set expectation on it like this:
procedure_service_instance = instance_double('ProcedureService', generate: some_value_you_want_to_be_returned)
expect(procedure_service_instance).to receive(:generate)
if you make your example method like this:
def method_example
data = Constant.fetch_from_cashe(cache_key)
procedure_service.generate
data
end
then you could git rid of receive_message_chain expectation and use:
expect(Constant).to receive(:fetch_from_cashe).with(cashe_key).and_return(expected_value)
expect_any_instance_of(ProcedureService).to receive(:generate){ some_fake_return_value }
also you can enable caching in your tests, check these links: link1, link2, link3
I do not know exactly where, and how your original is written, but based on the example method you provided, I think setting expectation on the methods that get sent would do the trick. and note that your goal is not to test rails cashing but to test that your code does use it as you want.
I have these code that executes a dynamic method. I'm using eval here to execute it but what I wanted to do is changed it to public_send because I was told so and it's much safer.
Current code:
# update workstep logic here.
incoming_status = params[params[:name]]
# grab workflow, this is current data, use this to compare status to in comming status
workflow = get_workorder_product_workstep(params[:workflow_id])
# check current status if its pending allow to update
# security concern EVAL!
if eval("workflow.can_#{incoming_status}?")
# update status
eval("workflow.#{incoming_status}")
# updated attribute handled_by
workflow.update_attributes(handled_by_id: #curr_user.id)
workflow.save
else
flash[:notice] = 'Action not allowed'
end
The eval here is the concern. How can I changed this to public_send?
Here's what I did.
public_send("workflow.can_#{incoming_status}?")
public_send("#{workflow}.can_#{incoming_status}?")
both of them doesn't work. gives me an error of no method. The first public error returns this undefined method workflow.can_queue? for #<Spree::Admin::WorkordersController:0x00007ff71c8e6f00>
But it should work because I have a method workflow.can_queue?
the second error on public is this
undefined method #<Spree::WorkorderProductWorkstep:0x00007ff765663550>.can_queue? for #<Spree::Admin::WorkordersController:0x00007ff76597f798>
I think for the second workflow is being evaluated separately? I'm not sure.
Working with public_send you can change the relevant lines to:
if workflow.public_send("can_#{incoming_status}?")
# update status
workflow.public_send(incoming_status.to_s)
# ...
A note about security and risks
workflow.public_send("can_#{xyz}?") can only call methods on workflow that are public and which start with the prefix can_ and end with ?. That is probably only a small number of methods and you can easily decide if you want to allow all those methods.
workflow.public_send("#{incoming_status'}) is different because it allows all public methods on workflow – even destroy. That means using this without the "can_#{incoming_status}?" is probably a bad idea. Or you should at least first check if incoming_status is in a whitelist of allowed methods.
eval is the worst because it will evaluate the whole string without any context (e.q. an object like workflow). Imaging you have eval("workflow.#{incoming_status}") without to check first if incoming_status is actually allowed. If someone then sends an incoming_status like this "to_s; system('xyz')"then xyz could be everything – like commands to send a hidden file via email, to install a backdoor or to delete some files.
I am very new to TDD in rails. I want to parse a JSON data and recursively call a test on the objects that is built from the hashed JSON data. The JSON data is built in such a way that the same object structure is repeated many times on several branch.
What I wanted to know is, is it possible to call the same test module recursively ? If yes then how can that be done ?
I figured out the solution, as I said earlier that I am pretty new to rails. The solution was quite simple.
I created a simple private function inside the test module which could be easly invoked from the test modules.
it "calls a recursive function" do
recursive_function()
end
private
def recursive_function()
...
end
I have a CommentList class with a static method fetch. The problem is, that it is not an ActiveRecord Model, but it makes http calls to fetch data.
class CommentList
def self.fetch
# http-foo here
return ['some', 'data']
end
end
Now I want an other model to use this fetch method and mock away the CommentList#fetch method to return a given dataset in my specs.
I only could find mocking gems that play together with a DB.
Am I totally overlooking something?
If you're using rspec, it should be easy to do it something like this:
CommentList.stub(:fetch => ['some', 'data'])
or to make it more of an expectation:
CommentList.should_receive(:fetch).and_return(['some', 'data'])
Another more elaborate solution would be to set up VCR. Basically what it does in this situation is the first time you run the test, CommentList would really hit the external http service and get back data. VCR then saves that response and from then on, it returns the cached response.
The good thing is that if you ever want to retest the external API call (maybe their API changed?), you just delete the VCR saved data, run your tests, and your tests will again run against the external service and cache fresh data.
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