We all know the importance of writing unit tests. But we also have seen many people who say that they take time to write, some things do not need to be tested, and all sorts of things that divide developer’s opinions as much as the endless debate ‘storyboards vs. programmatic views’.
But leaving personal preferences aside, I believe people develop some resistance to determined things because they might not understand or take time to understand those things. And it happens with me regarding automated testing.
The tests are useful to get fast confirmation that the code written works as expected and that when adding new features, nothing has been broken. Also, when working in a team, many different people might modify the project. So, once again, tests ensure the project’s integrity is maintained.
In the matter of unit tests, I am always intrigued, by testing the implementation of my code that makes use of third party dependencies. That’s the case of integrating Apple Pay, for example. That’s what I would like to talk about today.
The use cases for Apple Pay are pretty obvious. They very often involve the purchase of a product or service. And a wide range of apps are supporting it nowadays.
In order to make a payment using Apple Pay, there are some requirements. One of them is the creation of a
PKPaymentRequest (part of
PassKit framework), and the presentation of either a
PKPaymentAuthorizationController or a
As Apple docs state:
PKPaymentAuthorizationControllerclass performs the same role as the
PKPaymentAuthorizationViewControllerclass, but it does not depend on the
UIKitframework. This means that the authorization controller can be used in places where a view controller cannot (for example, in
watchOSapps or in
In this example I’ll use
PKPaymentAuthorizationViewController, since the project will only use it with
UIKit for now. In the case of the example, the app will present the authorization view controller only if the user can make payments with one of the specified networks, provided by the method
class func canMakePayments(usingNetworks: [PKPaymentNetwork]) -> Bool.
Following this idea, we can then create a
PaymentAuthorizationHandler class, that creates the payment request, and completes with failure in case the networks are not supported. Like so:
When I try to unit test this behavior, I stumble upon the class function, which could be a barrier in the tests.
Dependency injection to the rescue
One way overcome this limitation, is to use dependency injection to determine the possibility to make the payments that the specific test needs. I could either:
- Use constructor injection and set it in the initializer.
- Use property injection.
When possible, I prefer to use constructor injection. So this is how this is how the
PaymentAuthorizationHandler would be:
The typealias is nothing more than a closure representing the
class func canMakePayments(usingNetworks:) signature. But with this change, it’s possible to pass, in production, the real method, and for test use, the value required for the test case can be provided by injecting it during the test setup. This also makes the test deterministic.
A test is deterministic, or repeatable, if it produces the very same output when given the same input no matter how many times it is run.
Now, in the test, the SUT (System Under Test) will have the value injected upon creation. This is how the test for the failure scenario looks like:
Breaking it down:
1 - The test needs to simulate a scenario where networks are not supported, so the
sut is injected with a closure that returns
2 - An optional error is declared, so it can be captured in the
3 - The
receivedError is captured. In case an unexpected success message is received, the test simply fails with a message.
The next scenario to be covered is the case when the networks are supported, but the
PKPaymentAuthorizationViewController fails to be initialized.
The same approach will be used, but this time with the view controller initializer being injected via constructor injection. In case the initialization succeeds, the
requestAuthorization completes with success. This is the finished
Now, the following is happening:
1 - The initializer signature is extracted into the typealias
2 - An error case is declared to identify the cause of failure.
3 - The initializer now accepts a new parameter, which is the
ApplePayControllerFactory. In the case of production code,
PKPaymentAuthorizationViewController.init is used as default value.
requestAuthorization completes with the new error case failure, when the controller fails to be initialized.
5 - Finally the success case, the method completes with the initialized
And the tests follow the same style of the first one. Except that this time, to trigger an initialization failure, an empty (invalid) instance of
PKRequest was injected, rather than a valid request, that was injected for the success case test.
So, let’s break it down.
1 - An invalid request is passed to the authorization controller.
2 - The request can be processed by the networks, by passing a closure that returns
3 - There’s the assertion that when these conditions are presented, the failure output is
4 - In the success scenario, a valid request is passed.
5 - The request can be processed by the networks, by passing a closure that returns
6 - The test asserts that the receivedController is not
nil, as expected by the success case in the Result declared in
There it goes. This is a small example of how it is possible to manipulate the input for the tests, for implementing Apple Pay. Of course, there’s much more to it. There’s the display of
the received messages from
PKPaymentAuthorizationViewControllerDelegate, the communication with a payment provider, and other specifications, depending on the use case.
And of course, there must be countless ways of achieving similar results, but this was the one I got to, and wanted to share. If you have seen something different, or thought of a different approach, you are welcome to share with me on twitter. 😄
The full project, with a sample UIViewController, displaying the button and performing the
PKPaymentAuthorizationViewController can be found on this repository.