Dependency Injection: Providers
29 Jun 2014In the last post I introduced Dependency Injection as a way to decouple a class from the specific implementation of its dependencies. This way, we could replace those dependencies with mocks in a unit test.
But there is a special kind of dependency: sometimes a class A
needs to create an arbitrary number of instances of class B
and then uses them. It is clear that A
depends on B
but… how can we decouple it so that A
can be tested without using the actual instances of B
?
One approach would be overriding/swizzling somehow the alloc
method of B
in our tests: we leave the original implementation for our production code, but we stub the alloc method to return our mock on tests.
However, even if we managed to make this work, we would have to face another problem: class B
may have other dependencies. If this is the case… should A
receive all the dependencies of B
so that they can be injected into B
? That would mean making A
depend on stuff that it doesn't need, and therefore couples A
to B
's implementation.
Therefore, if we discard replacing the implementation of alloc
and we don't want to propagate our dependencies through our classes, we only can abstract object instantiation using providers.
A provider is just an intermediate class whose purpose is instantiating objects. That means that it's also its duty to store and inject the dependencies of the created objects.
Example
Continuing with the example of the previous post, imagine our SPTwitterMessageSender
implementation relays on some SPTwitterMessageSendCommand
which actually does the sending work.
Whenever we want to tweet a message, our sender will have to instance a command and execute it. These command instances will require access to the Twitter client in order to tweet the desired messages.
@interface SPTwitterMessageSender <SPMessageSending>
// Dependencies
@property (nonatomic, strong) TWTwitterClient *twitterClient;
@end
@implementation SPTwitterMessageSender
- (void)sendMessage:(NSString *)aMessage toUser:(FVUser *)user
{
SPTwitterMessageSendCommand *command = [[SPTwitterMessageSendCommand alloc] initWithTwitterClient:self.twitterClient];
[command execute];
}
@end
This makes SPTwitterMessageSender
depend on TWTwitterClient
just for injecting it in the command instances. If we wanted to change SPTwitterMessageSendCommand
to relay on some custom SPMyOwnTwitterClient
we would be forced to change SPTwitterMessageSender
, which makes no sense.
Let's see how to implement a provider for SPTwitterMessageSendCommand
:
@interface SPTwitterMessageSendCommandProvider
// Dependencies
@property (nonatomic, strong) TWTwitterClient *twitterClient;
- (SPTwitterMessageSendCommand *)command;
@end
@implementation SPTwitterMessageSendCommandProvider
- (SPTwitterMessageSendCommand *)command
{
return [[SPTwitterMessageSendCommand alloc] initWithTwitterClient:self.twitterClient];
}
@end
It's as simple as that.
Now let's see how it's used in SPTwitterMessageSender
:
@interface SPTwitterMessageSender <SPMessageSending>
// Dependencies
@property (nonatomic, strong) SPTwitterMessageSendCommandProvider *twitterMessageSendCommandProvider;
@end
@implementation SPTwitterMessageSender
- (void)sendMessage:(NSString *)aMessage toUser:(FVUser *)user
{
SPTwitterMessageSendCommand *command = [self.twitterMessageSendCommandProvider command];
[command execute];
}
@end
Now SPTwitterMessageSender
depends only on the provider and not on SPTwitterMessageSendCommand
dependencies.
If we want to test if a command is executed, we can write it like this:
- (void)testSendMessageShouldExecuteCommand
{
// Given
SPTwitterMessageSendCommand *mockCommand = mock(SPTwitterMessageSendCommand);
SPTwitterMessageSendCommandProvider *mockProvider = mock(SPTwitterMessageSendCommandProvider);
[[[mockProvider shouldReceive] command] andReturn:mockCommand];
SPTwitterMessageSender *sut = [[SPTwitterMessageSender alloc] initWithTwitterMessageSendCommandProvider:mockProvider];
// When
[sut sendMessage:@"this is a test" toUser:someUser];
// Then
[[mockCommand shouldReceive] execute];
}
As you can see, we have fixed both problems: we can easily replace our class with a mock and our SUT doesn't depend on our class' dependencies, just on the provider.
Providers with Typhoon
My friend and code wizard Daniel Rodríguez Troitiño developed, while we were working at Tuenti, a very useful addition to Typhoon: the TyphoonFactoryProvider
.
This class uses the runtime toolbox to implement these providers from the assembly file used by Typhoon, you don't need to manually inject the dependencies nor write the implementation of the factory method.
You only need to provide a protocol (the interface of your provider, with the factory method and the dependencies) and a block with the implementation of the factory method. That's all!
Conclusion
Object instantiation can easily become a seam by just creating an object provider whose only purpose is allocating the actual object.
Combining these providers with dependency injection (AKA making your dependencies explicit), it's really straightforward to replace that provider (and the provided object) in a test while keeping our classes loosely coupled.