Firebase user registration at iOS with integration tests
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.
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
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 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:
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.
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
updateChildValues Firebase methods expect a callback argument. This callback argument will have an
error?. And inside of it I will call our
completion callback passed through register method.
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:
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 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
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!
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