Improving the Testability of Apple Pay With Dependency Injection

5 minute read

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 PKPaymentAuthorizationViewController.

As Apple docs state:

The PKPaymentAuthorizationController class performs the same role as the PKPaymentAuthorizationViewController class, but it does not depend on the UIKit framework. This means that the authorization controller can be used in places where a view controller cannot (for example, in watchOS apps or in SiriKit extensions).

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 false.

2 - An optional error is declared, so it can be captured in the requestAuthorization method.

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 PaymentAuthorizationHandler:

Now, the following is happening:

1 - The initializer signature is extracted into the typealias ApplePayControllerFactory.

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.

4 - 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 PKPaymentAuthorizationViewController.

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 true.

3 - There’s the assertion that when these conditions are presented, the failure output is .unableToInitialize error.

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 true.

6 - The test asserts that the receivedController is not nil, as expected by the success case in the Result declared in PaymentAuthorizationHandler’s ApplePayCompletionHandler typealias.

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 PKPaymentButton,

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.

References

Payment Service Provider - Apple

Apple Pay Tutorial - Getting Started - Ray Wenderlich

Offering Apple Pay in Your App - Apple

Eradicating Non-Determinism in Tests - Martin Fowler