In my previous blogpost, How to test controllers by mocking dependencies in Vapor 3 and Swift, I injected dependencies into the controller using a constructor, but it turns out that there is a better way to do it. Vapor 3 introduces a dependency injection framework, which allows you to register, configure, and create your application’s dependencies in a maintainable way.
Show me the code ☀️
I was a little skeptical when I first read about it in the documentation. My main concern was that it would be hard to mock inside the tests. It turns out that it’s not.
The Service (vapor/service) framework provides you with a couple of interesting features:
Creating a new service in your controller is very simple, just call .make(_:)
on the request
and pass the type you want.
let availabilityChecker = try request.make(AvailabilityCheckerProtocol.self)
You have to remember that the class that conforms to registered protocol has to conform to the Service
protocol. You can achieve this by creating extension:
extension AvailabilityChecker: Service {}
The service protocol is just an empty protocol for declaring types that can be registered as a service.
The key to this is using protocols instead of the concrete implementation.
If you create a service using the class that implements your protocol, for example, like this:
let availabilityChecker = try request.make(AvailabilityChecker.self)
Inside the tests it will be impossible to switch AvailabilityChecker
for a mock object like AvailabilityCheckerMock
.
So initializing the service as a protocol allows you to register different implementations for the application and for testing.
For the application, your configure.swift
file will look like this:
services.register(AvailabilityCheckerProtocol.self) { container in
return AvailabilityChecker()
}
For testing, when you will use Vapor ➡️ helper, you can simply register mock in the configure closure like this:
final class AvailabilityTests: XCTestCase {
var app: Application?
override func setUp() {
super.setUp()
app = try! Application.makeTest(configure: { (config, services) in
services.register(AvailabilityCheckerProtocol.self) { container in
return AvailabilityCheckerMock()
}
}, routes: testRoutes)
}
Source code with those examples is available here: ⚙️Mocking-Dependencies-Vapor3.
As you can see, when creating services it’s much better to use protocols over the concrete implementation. When using protocols it’s very easy to mock all of the dependencies inside tests.
It’s definitely worth playing around and experimenting with the service framework. If you haven’t yet you should give it a try 👍
What do you think? Did you have any issues with testing while using services? Let me know - along with your questions, comments or feedback - on Twitter @mikemikina
Happy coding 😊