Ready for the first flight test

Firebase user registration at iOS with integration tests

Ronan Rodrigo Nunes
6 min readMar 15, 2017

In the previous post, I have talked about a good place in your project to put authentication rules. If you did not read the post you could click here.

After reading this post you will know about:

  • Gateways and Interface Adapters layer
  • Firebase authentication and storage
  • Firebase real world test (without mock)
  • Create a test target
  • Asynchronous integration tests

The first library

My intent in this project is to avoid third party libraries. The motivation is to learn about the Apple APIs using them in a simple and easy way. But now I will use Firebase instead of Cloud Kit to login and persist data.

I have a plan to migrate the authentication to ClouKit in the future. And will be nice to show how the layers and abstractions will help in this migration.

The Firebase service

Before jumping into code, we need to setup the Firebase service. It is very easy to configure. All you need is a google account, a working version of CocoaPods in your system and follow the Firebase setup wizard.

firebase.google.com

Authentication gateway

In the Clean Architecture the Gateway lives in the Interface Adapter layer. The code in this layers convert some data to a value object used to cross layers.

Also in this layer is any other adapter necessary to convert data from some external form, such as an external service, to the internal form used by the use cases and entities.
Martin, Robert. “The Clean Architecture” 2012

By The Dependency Rule, a use case should not know the gateway implementation. As I shown in the past post, the use case will know about a protocol, and not about behaviors of the gateway.

The overriding rule that makes this architecture work is The Dependency Rule. This rule says that source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle. In particular, the name of something declared in an outer circle must not be mentioned by the code in the an inner circle.
Martin, Robert. “The Clean Architecture” 2012

An example of dependency injection
Authentication Gateway Protocol

One of the advantages of the protocol is to mock then when testing the Register Use Case. Instead to use the gateway implementation with Firebase, we create a Test Double gateway.

Test Double is a generic term for any case where you replace a production object for testing purposes. There are various kinds of double that Gerard lists
Fowler, Martin. “TestDouble” 2006

The Gateway Test Double. It is not necessary to be faithful to the real gateway

The mocks are used to have a manual control of states in the tests. And the Register Use Case tests will look like the code bellow.

As you can see, I use the Result concept at the completion callback. This is an Enum that I copy from the Alamofire library, but my implementation is very simple:

This is sufficient for now

The Result enum is responsible for handling the responses. In the default approach, we can have an optional return of data or error, a success and failure case. This approach produces a matrix of possible cases to handling the responses. Is necessary to assert data? != nil plus successful state, and, error? != nil plus failure state.

The result enum allow us to only worry about success or the failure. And you could know more about this approach here.

Firebase errors

To not have my code wired to a framework I will not use the Firebase errors enum. This is a very simple enum with a very simple test too. And the motivation to do is not to be affected (at all) when I change the framework. In that way, my software is not wired to a framework.

Implementing Firebase gateway

This gateway implementation is responsible to authenticate the user and persists user data. These two processes are not made automatically by Firebase. So we must do authentication and persist the data.

The basic code to register an user is very simple. Use this FIREAuth instance and call the createUser method:

The createUser and updateChildValues Firebase methods expect a callback argument. This callback argument will have an user? and error?. And inside of it I will call our completion callback passed through register method.

Integration tests

For the Firebase comunication I created integration tests. Integration tests are a very optional implementation. Because we may be testing something that are already tested. It is slow to run and depends an integration.

Because of the differences from the unit tests, seems to be a good idea separate than from the unit test target. The main reason is to run the unit tests in the “speed of light” without waiting the integration tests. To create another target, we must do some configurations:

Create the Integration Tests Target
Remove unnecessary file
Rename the old BirthtalkTests target to BirthtalkUnitTests
Update Podfile
Tada! Separated tests

So these integration tests addresses two scenarios. First one is about successful registration and the other one is about what happens when things dont go well. This is my first time with asynchronous tests in Swift. To do that kind of test you must use an instance of XCTestExpectation.

The XCTestExpectation is used to wait for the end of asynchronous calls. It is possible to determine when the call ended with fulfill() method. At the end, you must write the assertions in a closure of waitForExpectations method. For example::

The second test was harder to do. Because I want to test the error handling when trying to register an user with the same email of already registered user in Firebase. So the first step is to create an user, and the second is to try to create the same user. The problem was in the first part. Where I create the user trying to create the second without waiting the first one creation. To do that, I place the second part in the completion callback of the first try.

It is necessary to configure FIRApp to run the tests. A FIRapp must be configured once. For that it is necessary to check if FIRApp.defaultApp() is nil and then configure it. Another thing we need to do is to delete the user at tearDown this will remove the user from the database

Disclaimer

Maybe you can see some code at my project that could be more reusable. But the idea is to not generate unnecessary/premature improvements. And for that, I will write reusable codes only when need it 😊.

In this post I do not use TDD. First, because I am not an extremist about TDD. Second is a new thing for me, asynchronous tests, and Firebase in Swift. But, after this implementation, I am more confident to use TDD, in a next similar scenario.

Don’t forget to follow the project on it’s Github page ⭐️. And iIf you like this post, share it on twitter, recommend it on medium, or both. Thanks!

References

Martin, Robert. “The Clean Architecture” 2012
https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

Fowler, Martin. “TestDouble” 2006
https://www.martinfowler.com/bliki/TestDouble.html

Duckett, Tim. “Testing Asynchronous Code in Swift” 2015
https://adoptioncurve.net/archives/2015/10/testing-asynchronous-code-in-swift

Alamofire. Elegant HTTP Networking in Swift
https://github.com/Alamofire/Alamofire

Nunes, Ronan Rodrigo. “VIPER — Arquitetura limpa em nossos APPs” 2015
https://medium.com/@ronanrodrigo/viper-arquitetura-de-software-e-carros-bee4a85c613f

--

--