Using Ruby's Struct to stub an object for minitest - ruby

This is working but it feels sloppy to me. I'm wondering if it is a code smell or if there is a better way to be accomplishing this result. Basic question is how to stub some arbitrary object in ruby.
I'm testing an edge case- that the final value of a parsing helper method correctly formats the result of a google analytics query (thus the odd assert statement) the incoming data is a google analytics object whose data is inside- essentially we have to call result.data["rows"]. The whole purpose of the struct here is to give my method's internals the ability to send that #data message. The test passes/fails appropriately but like I said, i'm wondering if this was the best way to go about it, for example getting my data out of the GA result object before sending it to be parsed.
my approach from the test- effectively it calls parse_monthly_chart_data(#ga_result)
def test_parse_monthly_chart_data_with_good_values
typical_data = {"rows" => [["0000", "194346"]...more arrays...]}
typical_vals = typical_data["rows"].to_h.values.map(&:to_i)
expected_result = typical_vals[-30..-1].inject(&:+)
Struct.new("GaResult") {def data; end }
#ga_result = Struct::GaResult.new
#ga_result.stub :data, typical_data do
assert_equal(ga.send(:parse_monthly_chart_data, #ga_result).flatten.last, expected_result)
end
end
Edit: I've solved for part of this issue by replacing stub with mocha's implementation. I'm still wondering if this is a code smell.

Not at all. I use this type of thing all the time. What you're using is called stubbing and using a Struct to accomplish this is no different than using a testing framework's implementation of a stub.
For further reading on mocks, stubbing, faking, etc. see this SO Question.

Related

How to mock mongodb in rspec (beginner)

I am an old programmer, but new to ruby, and thrown into an existing code base where I need to extend an rspec test.
The code that needs to be tested uses MongoDB (mongoid), and has a pattern similar to this:
objects = Database::MyTable.active.where(object_id: object_id).to_a
I want my rspec code to provide the objects hard-coded in the test. How can I do that?
Because of the "chain" of methods, stubbing is a little awkward, but it can be done.
allow(Database::MyTable).to(
receive(:active).and_return(double(
where: [{ id: 1 }, { id: 2 }]
))
)
We have stubbed active to return a mock (a double) on which we stub where.
Docs: rspec-mocks
PS: There are many other ways to write these stubs, some objectively better, some subjectively better. There are also ways to refactor your code to make stubbing easier.
PPS: Welcome to ruby!
I would separate the code that retrieves the data (including the line you mentioned) into one method and the code that consumes/operates on the data into another method, then mock the entire retrieval method. This way you don't need to muck with the exact queries used.

How to unit test a class that depends heavily on other classes?

My understanding is that unit testing should test classes in isolation, focusing on granular behavior and substituting objects of other classes using doubles/mocks wherever possible. (Please correct me if I'm wrong here.)
I'm writing a gem with a class called MatchList. MatchList::new takes two arguments, each an instance of another class called MatchPhrase. MatchPhrase contains some behavior that MatchList depends heavily on (i.e., if you feed anything other than a MatchPhrase to MatchList::new, you're going to get a bunch of "undefined method" errors).
My current (naive?) test setup uses let statements to assign variables for use in my examples:
let(:query) { MatchPhrase.new('Good Eats') }
let(:candidate) { MatchPhrase.new('Good Grief') }
let(:match_list) { MatchList.new(query, candidate) }
How do I write this unit test? Am I right in thinking it should be done without invoking the MatchPhrase class? Is that even possible?
For reference, here is what the MatchList class looks like:
class MatchList < Array
attr_reader :query, :this_phrase, :that_phrase
def initialize(query, candidate)
super(query.length)
#query = query
#this_phrase = query.dup
#that_phrase = candidate
find_matches until none?(&:nil?)
end
private
def find_matches
query.each.with_index do |this_token, i|
next unless self[i].nil?
that_token = this_token.best_match_in(that_phrase)
next if that_token.match?(that_token) &&
this_token != that_token.best_match_in(this_phrase)
self[i] = this_token.match?(that_token) ? that_token : NilToken.new
this_phrase.delete_once(this_token)
that_phrase.delete_once(that_token)
end
end
end
My understanding is that unit testing should test classes in isolation, focusing on granular behavior and substituting objects of other classes using doubles/mocks wherever possible. (Please correct me if I'm wrong here.)
In my understanding this is not true.
Using doubles/mocks has advantages and disadvantages.
Advantage is that you can take a slow service like database, email and mock it with fast performing object.
Disadvantage is that object that you are mocking is not "real" object and might surprise you and behave differently than real object would.
That's why it's always better to use real objects if practical.
Only use mocks if you want to speed up your tests or if it leads to much simpler tests. Even then have one test using real object to verify that it all works. This is called integration test.
Considering your case:
let(:query) { MatchPhrase.new('Good Eats') }
let(:candidate) { MatchPhrase.new('Good Grief') }
let(:match_list) { MatchList.new(query, candidate) }
There is really no advantage to mock query or candidate.
Mocking should be done for legitimate reasons and not as a matter of principle.
If there is only one collaborator class and your primary class is heavily coupled to it, mocking out the collaborator as a matter of principle may result in more fragility than benefit as the mock will not reflect the behavior of the collaborator.
Mocks and stubs are good candidates when you can reason against the mock's interface instead of an implementation. Let's ignore the existing code and look at the interfaces in use here:
MatchList.new takes a query and candidate
query is an Enumerable containing objects which implements best_match_in?(something)
The objects in query also implement delete_once(something)
candidate also implements delete_once(something)
best_match_in? returns something that implements match? and best_match_in?
Looking at the interfaces in use, MatchList appears to rely pretty heavily on the implementation of the query and candidate objects. Smells like a case of feature envy to me. Perhaps this functionality should reside within MatchPhrase instead.
In this case, I would write unit tests using actual MatchPhrase objects with a note to refactor this code.
Your understanding of using test part is correct. Its about focusing on granular behavior. E.g individual methods.
However to test the individual methods, try using doubles/mocks is not advisable. Marko ^^ has outlined the advantages / disadvantages of the mocks. I personally prefer not to not to use doubles/mocks as much as I can.
Its always a balance between the speed of your tests and the objects you create.
Before moving to doubles/mocks, its a good idea to see if you can write your test without saving the values to the DB. Like you have done before. That is faster than saving and retrieving the values from the DB.
One more thing is, private methods and not generally unit tested. This is with understanding of, the caller of your private method will have a unit test.
let(:query) { MatchPhrase.new('Good Eats') }
let(:candidate) { MatchPhrase.new('Good Grief') }
it 'check your expectation' do
expect(MatchList.new(query, candidate).query).to <check the expectation>
end
However I will re-evaluate the following points.
1 - do u want to call find_matches from initializer
2 - Let find_matches to return a value and then assign it to the #query variable (so that its easy to test the method with a return value)
3 - rename the query param in init to something else (just to avoid the confusion)
And the golden rule is If its hard to test (specially unit test), maybe your are doing something wrong.
HTH

Can I fake calling methods in Ruby tests?

I'm running some tests on my Ruby code, but the method I'm testing calls a function in an external library responsible for sending push notifications. I want the calls it makes to be 'faked', so they don't actually get called. It'd also be helpful if they could return a standard response, or yield with a standard response. Is there any way to do this?
Sure you can fake calling methods in Ruby tests. What you are looking for is creating so called mock code. It is entirely possible to return anything you want from such mock code.
You can create an identifier in Ruby which shadows another one - so for example you can create your own function shadowing one already existing in a library.
Google for the items written above in bold or ask more specific question, if needed.
The VCR gem makes it very easy to save your test suite's HTTP interactions and run them deterministically. That would be my suggested approach.
You can also, as others have pointed out, stub the method you are calling and manually specify its response. If you were using rspec, this would mean to add a line above the HTTP call in the test. Something along the lines of:
allow(Pusher).to receive(:message).and_return( "An object of your liking" )

What's better practice? Retrieve object or object.id?

This is more of a general question. And it might be dumb but since I constantly have this dilemma- decided to ask.
I have a function (in Rails if it matters) and I was wondering which approach is best practice and more common when writing large apps.
def retrieve_object(id_of_someobject)
# Gets class object ID (integer)
OtherObject.where('object_id = ?', id_of_someobject)
end
Here for example it receives 12 as id_of_someobject
OR
def retrieve_object(someobject)
# Gets class object
OtherObject.where('object_id = ?', someobject.id)
end
Here it gets class object and gets its ID by triggering the object attribute 'id'.
In this instance I would prefer the second approach. They may be functionally equivalent, but in the event that there's an error (e.g. calling nil.id), it makes more sense to handle that within the function so that it's easier to debug in the event of failure.
For the first approach, passing in nil wouldn't result in an error, but rather would return an empty array. So it might be difficult to know why your results aren't what you expected. The second approach would throw a flag and tell you exactly where that error is. If you wanted to handle that case by returning an empty array, you could do so explicitly.
As Michael mentioned, passing the whole object also gives you the flexibility to perform other operations down the road if you desire. I don't see a whole lot of benefit to evaluating the id and then passing it to a method unless you already have that ID without having to instantiate the object. (That would be a compelling use case for the first option)
Support both. It's only one more line and this way you don't have to remember or care.
def retrieve_object(id_or_someobject)
id = id_or_someobject.is_a?(SomeObject) ? id_or_someobject.id : id_or_someobject
OtherObject.where('object_id = ?', id)
end

is it possible to tell rspec to warn me if a stub returns a different type of object than the method it's replacing?

I have a method called save_title:
def save_title (data)
...
[ if the record exists, update, return 0]
[ if the record is new, create, return 1]
end
All fine, until I stubbed it:
saved_rows = []
proc.stub(:save_title) do |arg|
saved_rows << arg
end
The bug here is that I was using the integer returned from the real method to determine how many records were created vs. updated. The stub doesn't return an integer. Oooops. So the code worked fine in reality, but appeared broken in the test. A while later (more than I care to admit, cursing included) I realize the stub and the real method don't behave the same. Such are the pitfalls of dynamic languages I suppose.
Questions:
Can I tell rspec to warn me if the stub doesn't return the same sort of thing as the real method?
Is there an analyzer gem that I can use to warn about this sort of thing?
Is there some sort of best practice that I don't know about with returning values from methods?
1) There is no way that rspec can know what type of object the method is supposed to return, that's for you to tell it, however...
2) There is something you can look into. Instead of using a stub, try using a mock instead as your test double. It is basically the same thing as a stub, however, you can do many more validations on it (check out the documentation here). Things like how many times the specific method was called, the arguments it should be called with and what the return value should be as well. Your test will fail if any of those validations don't pass.
3) The best practice would be the method name itself. For example, methods ending in ? like object.exists? should always return a boolean value. In your case, I would suggest a refactoring of your method, maybe divide it in two, one for updating and one for creating and have another method to tell you if an object exists or not. It is not good practice to have a method behave in two different ways depending on the input (see separation of concerns)
Good luck! hope this helps.

Resources