compass Image by Jan VaŔek from Pixabay

Weā€™ve all been there: youā€™re on your typical developer day when suddenly a wild requirement appears! You proceed to wage the pros and cons of going for a in-house approach against using a battle tested framework by the community. Finally the later one wins and thus the development time for the feature gets shorter by a significant amount. However, how do we test something thatā€™s out of our control? šŸ¤”

Testing behavior, not functionality

Short answer is: we donā€™t, thatā€™s why we went down the road of a community adopted framework. But make no mistake my friend, Iā€™m not saying just because we selected a third-party framework that testing is out of the picture (far from it actually); what I meant is that given that the source code of it is black box for us, what we should aim to do is testing the interactions our project has with it produces in fact what we expect to. Letā€™s give it some more context so we donā€™t just keep this conversation in the imaginary abstract.

Letā€™s get a couple of things straight for starters

Disclaimer I: since third party dependencies integration to a swift project is not the main topic of this article, Iā€™m assuming you know how to do it (there are many way to achieve this, manyā€¦ many ways). Hence the starting point of this piece will be with Google maps already implemented in the project. There are plenty of posts out there explaining step by step the how and why of each, again: thatā€™s not the main focus here.

Disclaimer II: some may look at this and complain I didnā€™t choose Appleā€™s native map support and therefore Iā€™m getting myself into trouble to which I reply with a couple of arguments.

  1. The same principles Iā€™ll expose here apply as well to first party framework: theyā€™re all black box to use and we must test interaction/behavior rather than functionality. Apple themselves recommend to do so.
  2. I live and work for a Latin-American company that relies heavily on mapā€™s accuracy to provide optimal UX and Apple havenā€™t payed too much attention to this side of the world with its MapKit support so there you have it: GoogleMaps for the win šŸ—ŗ šŸ¤ŗ.

Our none cooperative requirement

Say youā€™re asked to show a map with some points of interest of yours (markers) in it and the marketing team wants to promote our appā€™s commercial effectiveness by showing to our partners how many tourists check their locations out -i.e.: number of clicks each one of them get-.

This looks pretty straight forward:

  1. Show a map.
  2. Load the partnerā€™s customized markers .
  3. Track userā€™s tapping on each of them by leveraging proper delegation exposed by the framework.

useCase

So far so good, ā€œwhatā€™s the problem with this code Mauri?ā€ you might be wonderingā€¦ Letā€™s put a simple test in place to see what I mean:

poorTest

We can use a spy here to assert proper tracking when the interaction occurs. However the first code smell is in line 14 where weā€™re forced to interact through Google mapā€™s delegate directly (actually from line with the frameworkā€™s import statement). This implies either leaving our map view in the view controller publicly accessible or creating a dummy one in the spot in order to satisfy the caller requirement. Not only does this sound cumbersome at best (irresponsible and sloppy at worst) but by definition unit testing should happen in small isolated chunks of code without external dependencies (data base queries, network calls or in this case third party frameworks interactions). The very fact that we must add import GoogleMaps in our tests is a very strong red flag.

Furthermore, when we take this one step deeper and leave our tests at the very bare minimum by stripping the Appā€™s main delegate from being called altogether we get this beautiful error:

noDelegate

Itā€™s a no brainer to solve it but when youā€™re working on a decentralized architecture where each feature is its own separate module, you donā€™t have the luxury of your own AppDelegate and it becomes a roadblock. Again you might be thinking ā€œyouā€™ve painted yourself into a corner by choice there Mauriā€ but many literature has been written about breaking monoliths and why you (or your team) should generally aim in that direction.

Enough talk, show me the code!

What Iā€™m about to share with you is an approach a fellow colleague of mine who has a lot more years than me in this taught me recently; what we need to do here is applying a facade that will interact on our behalf with the framework. A little bit of context from its wiki

Developers often use the facade design pattern when a system is very complex or difficult to understand because the system has many interdependent classes or because its source code is unavailable. This pattern hides the complexities of the larger system and provides a simpler interface to the client. It typically involves a single wrapper class that contains a set of members required by the client. These members access the system on behalf of the facade client and hide the implementation details.

Letā€™s take a look at how our view controller looks right now:

noDelegate

In red I marked the object to be replaced and in green thereā€™s the delegate to interface with. Letā€™s proceed with the first one:

mapViewProvidable

With this basic facade we accomplish plenty:

  1. Wrap the main mapā€™s object behind a vanilla UIView.
  2. Set camera position by handing it our own custom MapCamera object.
  3. Inject a MapProviderDelegate which in turn will communicate between the framework and us, achieving dependency inversion this way.
  4. Marker creation handling (this wasnā€™t view controllerā€™s concern in the first place).
  5. Finally, thereā€™s the mapWrapper object which type cast the instanced view as GMSMapView one (weā€™ll see why a little bit further).

MapProviderDelegate is a class protocol with a single method (our current business need demands only this from him but this approach allows flexibility to grow it without breaking compilation). Letā€™s inject this into our view controller to check how it looks:

viewControllerExplained

Itā€™s worth noting that as a default parameter for the object MapViewProvidable is an instance of MapViewProvider

Wait, is this magic? How does it work?

I had a teacher back at the university who used to say

As engineers, you should know how things work so you donā€™t think Harry Potter lives in your machines

Letā€™s pick inside MapViewProvider

mapProviderExplained

As we can see hereā€™s where all communication with Google mapā€™s framework and our delegate happens; in red are all external instances and in green our own objects (in fact, this is the only class that needs to import Google Maps). The secret sauce occurs between lines 9 and 13, as soon as the delegateā€™s MapProviderDelegate is set, mapWrapper is also set to the class itself thus propagating notifications whenever events from the framework are received (as seen in line 34 where only the information we care about is sent instead of an instance of GMSMapView as before)

Whatā€™s the point of adding this extra layer?

If youā€™re thinking the purpose of this extra layer is only aesthetics then think again, for starters I hate typing extra code just for the sake of coding. The power of this layer is that its type is an abstraction instead of a concrete implementation (inversion principle all over again) and thus it can be replaced on runtime by something elseā€¦ That something can be very well a Fake object. From Martin Fowlerā€™s blog:

Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production

This is perfect for us. All we need is to create a Fake object that implements the MapViewProvidable protocol with just enough code to fulfill compiling requirements like so:

fakeMap

Now our unit tests verify interaction as they should

properlyTest

Conclusion

Dependency inversion and dependency injection might sound as scary terms but whenever someone toast them around in a conversation just know they refer to making properties in a system be interfaces instead of a concrete type (conform to protocols in Swift) and also be able to pass them from place to place, thus enabling us to replace them by a form of test double when automatic testing take place.

You can check the entire project here. I really hope this strategy serve you guys, I didnā€™t find too much information related to the specific topic back when I was battling against this which is what ultimately lead me to write this piece (a little contribution back to the community). Until next time šŸ‘‹šŸ½


Tools used to make this writing a little bit faster and a little bit prettier:

  • Swift Mock Generator: saves you lot of time of mindless mock and spy objects typing
  • Nef: awesome tool for generating beautuful documentation
  • Skitch: simple image editor to add annotations