Issue #15 of objc.io is all about testing. It’s really good.
This is where behavior-driven development (BDD) comes it. It aims at solving these exact issues by helping developers determine what should be tested. Moreover, it provides a DSL that encourages developers to clarify their requirements, and it introduces an ubiquitous language that helps you to easily understand what the purpose of a test is.
Maybe I’m just not used to it, but I find it difficult to read BDD-style tests. There’s too much seemingly unnecessary DSL and the attendant block syntax.
We structure our tests by using the Given-When-Then pattern—every test is split into three parts.
The given section sets up the environment for the test by creating model objects or bringing the system under test to a certain state. The when section contains the code we want to test. In most cases, this is only one method call. In the then section, we check the result of our action: Did we get the desired output? Was the object changed? This section consists mainly of assertions.
This makes using mocks even more convenient. We can specify that a mock should be verified right at the point where we create that mock[…]
[We] added a new
dispatchGroupproperty to all managed object contexts. We then exclusively used
-performGroupedBlock:in all our code.
With this, we could wait for all asynchronous work to be done inside our
It may look odd to inject
NSUserDefaults, and that’s where this example may fall short. Remember,
NSUserDefaultsis standing in for a dependency that creates trouble. It would make more sense for the injected value to be an abstraction (that is, an
idsatisfying some protocol) instead of a concrete object. But I’m not going to discuss that in this article; let’s keep going with
NSUserDefaultsfor our examples.
We have five different forms of [dependency injection]. Each comes with its own set of pros and cons, so each has its place.
My advice for folks starting off with mock objects is to avoid using any mock object framework, at first, as you’ll have a better sense of what’s going on. My advice for folks starting off with DI is the same. But you can get even further in DI without a framework, relying solely on ‘Poor Man’s DI,’ where you do it yourself.
Private means private. Period. If you feel the need to test a private method, there is something conceptually wrong with that method. Usually it is doing too much to be a private method, which in turn violates the Single Responsibility Principle.
What To Do: Extract that private method to a separate class, give that class a properly defined contract, and test it separately. When testing code that relies on this new class, you can provide a test double of that class if needed.
Naturally, it’s not like these are two rival schools of programmers; you’d be hard-pressed to see a mockist and a statist dueling it out on the street. This dichotomy is useful, though, in terms of recognizing that there are times when mocks are and are not the most appropriate tools in your tool belt. Different kinds of tests are useful for different tasks, and the most effective test suites will tend to have a blend of different testing styles. Thinking about what you are trying to accomplish with an individual test can help you figure out the best approach to take, and whether or not fake test objects might be the right tool for the job.
Many experienced testers warn that you “shouldn’t mock what you don’t own,” meaning that you should only create mocks or stubs of objects that are part of your codebase itself, rather than third-party dependencies or libraries. There are two main reasons for this, one practical and one more philosophical.
Regardless of your method of testing, when testing user behavior, you want to stay as close to the user as possible. You want to make it appear to your code as if the user is interacting with it. Imagine the user is looking at a view controller, and then taps a button, which presents a new view controller. You’ll want your test to present the initial view controller, tap the button, and verify that the new view controller was presented.
By focusing on exercising your code as if the user had interacted with your app, you verify multiple things at once. Most importantly, you verify the expected behavior. As a side effect, you’re also simultaneously testing that controls are initialized and their actions set.
CALayersubclass and renders it to a
UIImage. This snapshot is used to create tests that compare a saved snapshot of the view/layer and the version generated by your test. When it fails, it will create a reference image of the failed test, and another image to show the difference of the two.
Stay up-to-date by subscribing to the Comments RSS Feed for this post.