Where to put long mock/stub responses in rspec rails - ruby-on-rails

api = double "myApi"
api.should_receive(:get_info).and_return({
# a 360 lines hash!
})
I want to provide the response that should return from this double.
But this response is a VERY long hash, and I don't want to clutter my spec file.
Instead I want to write the hash in separate file, and use it in my spec
So, What is the best practices around ?

The best practise would be to not stub out the whole hash. Surely your tests wont require each and every line. You would be better off stubbing out the few lines that each test will need in each test / context.
If you must stub the whole api, you can create a separate module in spec/support:
# spec/support/api_stub.rb
module ApiStub
def self.response
{
# hash
}
end
end
Since anything in that folder gets included automatically, you can then use ApiStub.response in your stub definition.

Related

RSpec + Rubocop - why receive_message_chain is a code smell?

I am about to write specs for my custom validator, that uses this chain to check if a file attach with ActiveStorage is a txt:
return if blob.filename.extension.match?('txt')
Normally, I would be able to stub it with this call:
allow(attached_file).to receive_message_chain(:blob, :byte_size) { file_size }
Rubocop says it is an offence and points me to docs: https://www.rubydoc.info/gems/rubocop-rspec/1.7.0/RuboCop/Cop/RSpec/MessageChain
I would have to declare double for blob and byte_size and stub them in separate lines, ending up with 5 lines of code instead of 1. Am I missing something here?
Why should I avoid stubbing message chains?
I would have to declare double for blob and byte_size and stub them in separate lines, ending up with 5 lines of code instead of 1.
This is, in fact, the point. Having those 5 lines there likely will make you feel slightly uneasy. This can be thought of as positive design pressure. Your test setup being complex is telling you to have a look at the implementation. Using #receive_message_chains allows us to feel good about designs that expose complex interactions up front.
One of the authors of RSpec explains some of this in a GitHub issue.
What can I do instead?
One option is to attach a fixture file to the record in the setup phase of your test:
before do
file_path = Rails.root.join("spec", "fixtures", "files", "text.txt")
record.attribute.attach(io: File.open(file_path), filename: "text.txt")
end
This will test the validator end-to-end, without any stubbing.
Another option is to extract a named method, and then stub that instead.
In your validator:
def allowed_file_extension?
blob.filename.extension.match?("txt")
end
In your test:
before do
allow(validator).to receive(:allowed_file_extension?).and_return(true)
end
This has the added benefit of making the code a little clearer by naming a concept. (There's nothing preventing you from adding this method even if you use a test fixture.)
Just as a counterpoint, I frequently get this rubocop violation with tests around logging like:
expect(Rails).to receive_message_chain(:logger, :error).with('limit exceeded by 1')
crank_it_up(max_allowed + 1)
I could mock Rails to return a double for logger, then check that the double receives :error. But that's a bit silly, IMO. Rails.logger.error is more of an idiom than a message chain.
I could create a log_error method in my model or a helper (and sometimes I do), but often that's just a pointless wrapper for Rails.logger.error
So, I either end up disabling RSpec/MessageChain for that line, or perhaps for the entire project (since I would never abuse it for real...right?) It would be nice if there was a way to be more selective about disabling/muting this cop across the project...but I'm not sure how that could work, in any case.

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.

Why do I get "#to_hash is deprecated. Use #to_h instead" when testing with RSpec?

I'm starting with Rails, and I'm trying to get used to RSpec writing some tests. In this particular example, I'm trying to test a class that performs calls to twitter api. As it has a small ad-hoc cache, I'm trying to test that the API is only called when it's necessary or that it returns the currect kind of object.
I'm using a Ruby Twitter client.
before do
#manager = TwitterService.new
#manager.client = double()
hash = JSON.parse('{"id":4}', :symbolize_names => true)
allow(#manager.client).to receive(:friends) { [Twitter::User.new(hash)] }
end
it 'returns a collection of friends' do
#result.each do |r|
expect(r).to be_a Twitter::User
end
end
As far as I've been able to see, no error or warnings are being thrown in execution, but when I run rspec I get:
/home/myuser/.rvm/gems/ruby-2.1.0/gems/activesupport-4.1.1/lib/active_support/core_ext/object/json.rb:48:in `as_json':
[DEPRECATION] #to_hash is deprecated. Use #to_h instead.
I don't even see where to_hash is being used. I guess that it can be internally used by JSON.parse but if that was true, I don't know how to change that. Even everything seems to work, these warnings affect heavily to tests readability.
Yes, probably your JSON.parse is using the as_json function internally, and it calls to_hash. On rails' source code it's actually like this:
class Object
def as_json(options = nil) #:nodoc:
if respond_to?(:to_hash)
to_hash.as_json(options)
else
instance_values.as_json(options)
end
end
end
I suppose you could monkeypatch that, if that's critical to you, and check rails' issues on github, to either follow how it's going or create one yourself.

How do I test `rand()` with RSpec?

I have a method that does something like this:
def some_method
chance = rand(4)
if chance == 1 do
# logic here
else
# another logic here
end
end
When I use RSpec to test this method, rand(4) inside it always generates 0. I am not testing rand method of Rails, I am testing my method.
What is a common practice to test my method?
There are two approaches I would consider:
Approach 1:
Use a known value of seed in srand( seed ) in a before :each block:
before :each do
srand(67809)
end
This works across Ruby versions, and gives you control in case you want to cover particular combinations. I use this approach a lot - thinking about it, that's because the code I was testing uses rand() primarily as a data source, and only secondarily (if at all) for branching. Also it gets called a lot, so exerting call-by-call control over returned values would be counter-productive, I would end up shovelling in lots of test data that "looked random", probably generating it in the first place by calling rand()!
You may wish to call your method multiple times in at least one test scenario to ensure you have reasonable coverage of combinations.
Approach 2:
If you have branch points due to values output from rand() and your assertions are of the type "if it chooses X, then Y should happen", then it is also reasonable in the same test suite to mock out rand( n ) with something that returns the values you want to make assertions about:
require 'mocha/setup'
Kernel.expects(:rand).with(4).returns(1)
# Now run your test of specific branch
In essence these are both "white box" test approaches, they both require you to know that your routine uses rand() internally.
A "black box" test is much harder - you would need to assert that behaviour is statistically OK, and you would also need to accept a very wide range of possibilities since valid random behaviour could cause phantom test failures.
I'd extract the random number generation:
def chance
rand(4)
end
def some_method
if chance == 1 do
# logic here
else
# another logic here
end
end
And stub it:
your_instance.stub(:chance) { 1 }
This doesn't tie your test to the implementation details of rand and if you ever decide to use another random number generator, your test doesn't break.
It seems that best idea is to use stub, instead of real rand. This way you would be able to test all values that you are interested in. As rand is defined in Kernel module you should stub it using:
Kernel.stub(:rand).with(anything) { randomized_value }
In particular contexts you can define randomized_value with let method.
I found that just stubbing rand ie. using Kernel.stub(:rand) as answered by Samuil did not initially work. My code to be tested called rand directly e.g
random_number = rand
However, if I changed the code to
random_number = Kernel.rand
then the stubbing worked.
This works in RSpec:
allow_any_instance_of(Object).to receive(:rand).and_return(1)

How to fixture data for tests that are not in DB

Let say I want to test my controller behavior, but it accepts JSON string via GET.
Right now I have var in my test class #testJson, but from time to time some unexpected stuff happens to those JSONS (bad char inside i.e.). So I want to add another test case.
But adding another var #problematicJson1 (and there could be more probably) doesn't seems like a good idea.
What's is the best way to keep "fixtures" like that? Should I keep'em in files and load them? Is there some fixture feature i don't know about that could help?
Those things are not fixtures.
You should use a neat feature of RSpec (if you are using RSpec at all) that allows to lazily define variables, so the actual variable is instantiated only when used by a specific "it", even if it is defined in an outer "context/describe" block.
https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let
context "some context" do
let(:testJson) { put your json inside the block }
let(:otherJson) { {:my_json => textJson} } # this will use the defined testJson
it "something" do
testJson.should have_key "blah"
end
context "some internal context"
let(:testJson) { something else }
it "some other test" do
otherJson[:my_json].should ....
# this will use the local version of testJson
# you only have to redefine the things you need to, unlike a before block
end
end
end

Resources