Let me start by saying I’ve never been a huge fan of rspec. I think the magic it provides shields new developers from learning and understanding how ruby works, learning and understanding is partly what unit testing aims to offer in the first place. Over the years I’ve decided that if a mock or a stub is necessary to test a piece of code that I would simply write it using plain old ruby and where necessary using using Minitest::Mock.
The easiest way to test your code is to design it with with testability as a concern. To be clear; if you can write code that is clear, concise, and testable that’s great. However, you should not sacrifice clear and concise code just to make it testable.
One thing I do to make my code more testable, is, I try to pass in dependencies where possible (dependency injection) vs allowing the class itself to instantiate another object. This allows the developer to easily pass in a mock object. Here is a concrete example:
If you wanted to write the same code, but write it with testability in mind you would write it the following way.
This is a very very very simple example, but the difference is huge when it comes to testability. We would like to use a mock object in place of the SimpleObject class while testing both the DifficultToTest class and the EasyToTest class. To test the DifficultToTest class with a mock for the SimpleObject we would have to do the following using something like minitest.
If it’s not clear why we are using a mock for this test then let me explain a bit more. We are using a mock to isolate the function of the SimpleObject class from the DifficultToTest class. If for example say_hello was a function that required a network call or some lengthy computation, you wouldn’t want to call it over and over again in your tests. You’d want to test that one lengthy function call once and then in all other cases use the mock object. Keeping tests fast is what keeps you running tests often and running tests often is the key to making sure they are actually passing and your code isn’t broken.
Back to the second example. With the SimpleToTest class we would write the test in the following way.
I hope it is clear to see, that the second example is far clearer and easier to understand. This would be even more evident if we had multiple dependencies inside of the greeting method. One thing I am missing in this example is expectation checking. In a simple case I would likely do the following if I wanted to test that the say_hello method was actually called.
now we have a test that has no use of an external magic mocking and stubbing library yet we have all the benefit and clear understanding of how the ruby language works. That’s all for now.