Using Dependency Injection to Mock Network API Service in View Controller

Alfian Losari
Jun 27 · 6 min read
GameDB App with Dependency Injection

Dependency Injection is a software engineering technique that can be used to pass other object/service as a dependency to an object that will use the service. It’s sometime also called inversion of control, which means the object delegates the responsibility of constructing the dependencies to another object. It’s the D part of the SOLID design principles, dependency inversion principle.

Here are some of the advantages of using dependency injection in your code:

1. Better separation of concerns between construction and the use of the service.

2. Loose coupling between objects, as the client object only has reference to the services using Protocol/Interface that will be injected by the injector.

3. Substitutability, which means the service can be substituted easily with other concrete implementation. For example, using Mock object to return stub data in integration tests.

How to use Dependency Injection

There are 3 approaches that we can use for implementing dependency injection in our class:

  1. Using Initializer. With initializer, we declare all the parameter with the interface that will be injected into the class. We assign all the parameters to the instance properties. Using this approach we have the option to declare all the dependent instance properties as private if we want.
class GameListViewController: UIViewController {  private let gameService: GameService  init(gameService: GameService) {
self.gameService = gameService
}
}

2. Using Setter. With setter, we need to declare the dependent instance properties as internal or public (for shared framework). To inject the dependencies, we can just assign it via the setter.

class GameListViewController: UIViewController {  var gameService: GameService!}let gameVC = GameListViewController()
gameVC.gameService = GameService()

3. Using Method. Here, we declare a method with all the required parameters just like the initializer method.

class GameListViewController: UIViewController {  private var gameService: GameService!  func set(_ gameService: GameService) {
self.gameService = gameService
}
}

The most common approaches being used are the injection via initializer and setter.

What we will Refactor

Next, let’s refactor a simple GameDB app that fetch collection of games using the IGDB API. To begin, clone the starter project in the GitHub repository below:

To use the IGDB API, you need to register for the API key in IGDB website below.

Put the API Key inside the IGDBWrapper initializer in the GameStore.swift file Try to build and run the app. It should be working perfectly, but let’s improve our app to the next level with dependency injection for better separation if concerns and testability. Here are the problems with this app:

1. Using GameStore class directly to access singleton. Using singleton object is okay, but the View Controllers shouldn’t know about the type of the concrete class that has the singleton.

2. Strong coupling between the GameStore class and View Controllers. This means View Controllers can’t substitute the GameStore with mock object in integration tests.

3. The integration tests can’t be performed offline or stubbed with test data because the GameStore is calling the API with the network request directly.

Declaring Protocol for GameService

To begin our refactoring journey, we will create a protocol to represent the GameService API as a contract for the concrete type to implement. This protocol has several methods:

  1. Retrieve list of games for specific platform (PS4, Xbox One, Nintendo Switch).
  2. Retrieve single game metadata using identifier.

In this case, we can just copy the methods in the GameStore to use it in this protocol.

Implement GameService Protocol in GameStore class

Next, we can just implement the GameService protocol for the GameStore class. We don’t need to do anything because it already implemented all the required method for the protocol!.

class GameStore: GameService {
...
}

Implement Dependency Injection for GameService in GameListViewController

Let’s move on to the GameListViewController, here are the tasks that we’ll do:

  1. Using initializer as the dependency injection for the GameService and Platform properties. In this case for GameService , we need to declare a new instance property to store it. For storyboard based view controller, you need to use setter to inject dependency.
  2. Inside the initializer block, we can just assign all the parameter into the instance properties.
  3. Inside the loadGame method, we change the singleton invocation to the GameService instance property to fetch the games for the associated platform.

Next, we need to update the initialization of GameListViewController in the AppDelegate . Here, we just pass the platform and the GameStore instance to the initializer parameter like so.

Implement Dependency Injection for GameService in GameDetailViewController

Let’s move on to the GameDetailViewController, here are the tasks that we’ll do, it will pretty similar to GameListViewController changes:

  1. Using initializer as the dependency injection for the GameService and id properties. In this case for GameService , we need to declare a new instance property to store it. For storyboard based view controller, you need to use setter to inject dependency.
  2. Inside the initializer block, we can just assign all the parameter into the instance properties.
  3. Inside the loadGame method, we change the singleton invocation to the GameService instance property to fetch the games for the associated platform. Also, we don’t need to unwrap the optional game id anymore!

Next, we need to update the initialization of GameDetailViewController in the GameListViewController . Here, we just pass the id and the GameStore instance to the initializer parameter like so.

Create MockGameService for View Controller integration Test in XCTest

Next, create a new target and select iOS Unit Testing Bundle from the selection. This will create a new target for testing. To run the test suite, you can press Command+U .

Let’s create a MockGameService class for mocking game service in view controller integration tests later. Inside each method, we just return the stub hardcoded games for stub data.

Create GameListViewController XCTestCase

Next, let’s create test cases for GameListViewController , create a new file for unit test called GameListViewControllerTests.swift . Inside the sut (system under test) will be the GameListViewController . In the setup method we just instantiate the object and also the MockGameService object then assign it to the instance properties.

The first test is to test whether the GameService is invoked in the viewDidLoad method of GameListViewController . We create a function test case called testFetchGamesIsInvokedInViewDidLoad . Inside we just trigger the invocation of viewDidLoad , and then check the MockGameService isFetchPopularGamesInvoked is set to true for the assertion.

The second test is to test whether the fetchPopularGames method invocation fills the collection view with stub data. We create a function called testFetchGamesReloadCollectionViewWithData . Inside, we trigger the invocation of viewDidLoad and assert the number of items in the collection view is not equal to 0 for the test to pass.

Try to build and run all the tests with Command+U to view all the green symbols which means all tests passed successfully 😋!. For challenge, try to create the test cases for GameDetailViewController 🥰 using the MockGameService!.

You can clone the end project in the GitHub repository below:

Conclusion

Dependency injection is a really important software design principle that really help us to write more loosely coupled code between modules that lead to improved maintainability and flexibility in our codebase as the complexity of our app grow.

We can also use a centralized container to build our dependencies, and then resolve the dependencies only we require them in our class. This is really helpful, if our app navigation hierarchy is quite deep as we only need to pass the container down. So let’s keep the lifelong learning goes on and write better code.

AppCoda Tutorials

A great collections of Swift and iOS app development tutorials. To contribute, tweet us @appcodamobile

Alfian Losari

Written by

Mobile, Web, a bit of backend Software Developer and Lifelong Learner. Currently building super app @ Go-Jek.

AppCoda Tutorials

A great collections of Swift and iOS app development tutorials. To contribute, tweet us @appcodamobile

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade