Today I watched a talk “Real World Mocking in Swift” by Veronica Ray hosted on realm.io. Mocks are something that I have found a TON of use for while striving to reach that elusive 100% code coverage from my unit tests. I have conflict on my mind on what the “right” amount of mock usage is, and I think Veronica hit a lot of important points in her talk.
A Tradeoff of Using Mock Objects
When using mock objects in your unit tests, there’s certainly a point at which readability and maintainability of your tests suffers in exchange for reaching 100% coverage. The tradeoff in my mind though, which is where I end up okay with these sacrifices, is that I rarely find the need to go back and change the code under test in such a minor way that the resulting changes to the tests are either minor tweaks, or major surgery to rewrite the tests. To say that another way, I feel better about the higher code coverage, despite less maintainable tests.
I Miss You OCMock
I used the hell out of OCMock during my days as an Objective-C developer. I loved how easy its API enabled me to quickly mock out 3rd party classes in order to test edge cases in my own code. I was bummed when I learned that the runtime hackery that enables OCMock simply isn’t possible in Swift. Since then though, I’ve come around to making my own mocks. It really isn’t that hard, it does take a little but more time, but I think it forces me to make some more protocol oriented choices in your code than I would otherwise have thought about.
Benefits of Mock Objects
Veronica specifically calls out three benefits of using mock objects that I totally agree with:
- It will make your tests faster.
- It will increase the coverage of your test suite.
- It will make your tests more robust.
Regarding #1, imagine you have a complicated method that runs a time consuming algorithm to tell you if some model object matches some criteria. When writing a unit test for a class that uses this algorithm, you don’t want to have to rely on this slow processing each time your tests run. By using mocks, you can simply force a response of
false depending on what you’re trying to test, BOOM, FASTER!
As #2 states, mocks will increase the coverage of your test suite. I find this to be true especially with testing error handling. Often, some third party code I’m using will provide hooks for custom error handling in the event that things go wrong (imagine a failed object initialization). Without needing to reverse engineer the third party code, and intentionally code up a scenario in which the error handling will be triggered, you can simply code up a mock that forces the specific error scenario to trigger so you can write tests for your error handling code, BOOM, MORE TEST COVERAGE!
Along the same lines as writing a mock to simulate an error case for a failed web service connection, simply using a mock to provide a static API response is a great way that the #3 benefit is realized. You can then write tests that don’t depend on making a slow network connection and instead use the 100% reliable mock to always simulate expected response.
I don’t think anyone would dispute the value of mocks. I think it’s more of a cost/benefit tradeoff in one’s mind to buy in that the additional amount of coding is worth it.
Going Deeper on Mocking in Swift
There are a couple other topics that jumped out at me in this video that I’m going to save for dedicated posts throughout the rest of the week. Here’s a teaser:
- My reaction to Veronica’s claim that partial mocks are an antipattern.
- Veronica gives some guidelines as to “what makes a good mock.” I agree, and I’ll give you an anecdote of a time that I learned this lesson the hard way.
- Veronica talks about dependency injection. I learned of a cool way to do this with protocols when needing a different implementation per target.
- My opinion of using the term “mocking” to mean anything that is a “test double.”
- Veronica references some Swift mocking frameworks. I’ll provide getting started examples.