Use_the_InMemoryLocationService – Dependency Injection

Next, we use the in-memory location service to compose the controller like this:

var inMemoryLocationService = new InMemoryLocationService();
var devController = new InjectAbstractionLocationsController(
    inMemoryLocationService);

As we can see from the preceding code, we injected a different service into the controller, changing its behavior. This time, after calling the GetAsync method, the controller returned the ten Location objects from the InMemoryLocationService.The visual representation of our object tree is as follows:

 Figure 8.2: Composition of the controller in a test that injects an InMemoryLocationService instance.Figure 8.2: Composition of the controller in a test that injects an InMemoryLocationService instance. 

It is harder to write assertions for the preceding test because we inject an instance of the InMemoryLocationService class, which ties the result to its implementation. For this reason, we won’t look at that code here. Nonetheless, we succeeded at composing the controller differently. Let’s have a look at the last test case.

Mock_the_ILocationService

The last unit test mocks the ILocationService directly. The mock service returns a collection of one item. That item is the Location instance referenced by the ExpectedLocation property. Here’s that code:

var locationServiceMock = new Mock<ILocationService>();
locationServiceMock.Setup(x => x.FetchAllAsync(It.IsAny<CancellationToken>())).ReturnsAsync(() => new Location[] { ExpectedLocation });
var testController = new InjectAbstractionLocationsController(
    locationServiceMock.Object);

When executing the GetAsync method, we get the same result as in the first test case: a collection of a single test Location object. We can assert the correctness of the method by comparing values like this:

Assert.Collection(result,
    location =>
    {
        Assert.Equal(ExpectedLocation.Id, location.Id);
        Assert.Equal(ExpectedLocation.Name, location.Name);
    }
);

We can also leverage Moq to verify that the controller called the FetchAllAsync method using the following code:

locationServiceMock.Verify(x => x
    .FetchAllAsync(It.IsAny<CancellationToken>()),
    Times.Once()
);

The object tree of is very similar to the previous diagram but we faked the service implementation, making this a real unit test:

 Figure 8.3: Composition of the controller in a test that mocks the ILocationService interface.Figure 8.3: Composition of the controller in a test that mocks the ILocationService interface. 

As we explored in this project, with the right design and dependency injection, we can easily compose different object trees using the same building blocks. However, with a bad design, it is hard to impossible to do so without altering the code.

As you may have noticed, we used the new keyword in the controller to instantiate the DTO. DTOs are stable dependencies. We also explore object mappers in Chapter 15, Object mappers, Aggregate Services, and Façade, which is a way to encapsulate the logic of copying an object into another.

Let’s conclude before our next subject.

Conclusion

In this section, we saw that the strategy pattern went from a simple behavioral GoF pattern to the cornerstone of dependency injection. We explored different ways of injecting dependencies with a strong focus on constructor injection.Constructor injection is the most commonly used approach as it injects required dependencies, which we want the most. Method injection allows injecting algorithms, shared states, or contexts in a method that could not otherwise access that information. We can use property injection to inject optional dependencies, which should rarely happen.You can see optional dependencies as code smells because if the class has an optional role to play, it also has a primary role resulting in dual responsibilities. Moreover, if a role is optional, it could be better to move it to another class or rethink the system’s design in that specific area.To practice what you just learned, you could connect the code sample to a real database, an Azure Table, Redis, a JSON file, or any other data source—tip: code classes that implement the ILocationService interface.

As we covered, we can inject classes into other classes directly. There is nothing wrong with that. However, I suggest injecting interfaces as your initial approach until you are confident that you have mastered the different architectural principles and patterns covered in this book.

Next, we explore guard clauses.

Leave a Reply

Your email address will not be published. Required fields are marked *



         


          Terms of Use | Accessibility Privacy