After a somewhat long delay, it’s time to finally continue our series on clean architecture. This is the second post in the inner series in which we show you a quick implementation of said architecture and the third post in the overall series. In case you haven’t read the previous posts, please do so by using the links in the series layout below:
- An Introduction To Clean Architecture
- A Clean Architecture Example in C#, part 2 (this post)
Without further ado, let’s continue our implementation.
Wrapping Up the First Use Case
If you recall the previous post, you’ll remember that we created the class for the first use case. But we haven’t fully implemented it yet since we’ve pretty much stubbed out all of the classes and methods that the use case was supposed to use. Now, let’s go back and provide real implementations for all the supporting classes in order for our use case to work properly.
If we go now and just try to build the app in its current form, it won’t work. And that’s because our code lacks a lot of references. For instance, we’ve just added the file for the “Task” entity, but we haven’t properly referenced the “Domain” project from the “UseCases” project. Remember that “Domain” doesn’t reference—actually, it isn’t even aware of—any other project. That way, our application will remain faithful to the dependency inversion principle, the “D” in SOLID.
With that out of the way, let’s finish the first use case. In the previous post, a commenter pointed out that I didn’t write my tests first. That is, to my shame, correct. In order to atone for that, I’ll finish the implementation of “AddTask” in true TDD fashion, starting with failing tests by making them pass and then refactoring if needed.
I’ll start out by creating a new project that will store my unit tests for the “UseCases” project. I’ll call it “UsesCases.Test,” like in the image below:
Now, instead of deleting the default class as I did in the previous post, I’ll just rename it and use it to store my tests for the first use case.
Next, it’s time to install NUnit and make the test project reference the production one. I’ll leave that out for the sake of brevity. If you don’t know the drill, there are resources out there that cover this.
We’re now ready to go. Time to create our first test. Many people start out by covering the happy paths first. I tend to do the opposite and begin with the degenerate cases. S, we’ll start by covering the scenario where someone tries to add a task with an empty title.
Our test method will be called “Creation_RequestWIthEmptyTitle_Fails,” following Roy Osherove’s naming convention for tests. The complete code for the test is what follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[Test] public void Creation_RequestWIthEmptyTitle_Fails() { IClock fakeClock = new StoppedClock(); ITaskRepository repo = new FakeTaskRepository(); var request = new AddTaskRequest { Title = string.Empty, DueDate = DateTimeOffset.Now }; var sut = new AddTask(request, repo, clock); var expected = AddRequestResult.Fail("You're trying to add a task with an empty title."); Assert.AreEqual(expected, sut.Execute()); } |
It shouldn’t come as a big surprise that this doesn’t compile. For starters, we’re referencing two classes that don’t even exist: “StoppedClock” and “FakeTaskRepository.” These classes are supposed to be test doubles (more specifically fakes) that we’ll provide the constructor of AddTask. We won’t be implementing the real classes for a while. And this is a good thing because we are able to delay the implementation of infrastructure concerns like the database access layer.
But even so, we need to implement at least our fakes, right? Let’s do it then, in the quickest and easiest possible way. And by that I mean let’s make use of Visual Studio’s conveniences. I’ll just hover with the cursor over the names of the non-existing classes, wait for the lightbulb icon to show up, and click on that handy message that lets me generate a class in a new file.
We’ll do this for both “StoppedClock” and “FakeTaskRepository.” It’s important to notice here that while the interfaces live in the “UseCases” namespace, the implementation itself will reside in the test project. That makes sense when you consider that these implementations only exist for the sole purpose of enabling unit tests. There’s no reason for them to be available in the production assembly.
Anyway, now we must go to the files VS generated for us and make the classes actually implement the interfaces they’re supposed to implement. The code for “StoppedClock” ended up looking like this:
1 2 3 4 5 6 7 8 9 10 11 |
internal class StoppedClock : IClock { private readonly DateTimeOffset dateTime; public StoppedClock(DateTimeOffset dateTime) { this.dateTime = dateTime; } public DateTimeOffset OffsetNow => dateTime; } |
Its implementation matches its name, right? It’s literally a stopped clock since it always gives the same date and time. What about the code for the fake repository? Here it goes:
1 2 3 4 5 6 7 |
internal class FakeTaskRepository : ITaskRepository { public void Save(Task task) { } } |
Our fake repository does literally nothing! Well, since the contract for “ITaskRepository” defines that Save doesn’t return anything, doing nothing will suffice for now.
After a little bit of work, the code for our test method looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[Test] public void Creation_RequestWIthEmptyTitle_Fails() { IClock clock = new StoppedClock(DateTimeOffset.Now); ITaskRepository repo = new FakeTaskRepository(); var request = new AddTaskRequest { Title = string.Empty, DueDate = DateTimeOffset.Now }; var sut = new AddTask(request, repo, clock); var expected = AddRequestResult.Fail("You're trying to add a task with an empty title."); Assert.AreEqual(expected, sut.Execute()); } |
It’s finally compiling. But does the test itself pass? Well, bad news then.
And why is it failing? Because the “Fail” method in the “AddRequestResult” still throws a “NonImplementedException” instead of, you know, doing some real work. Besides, the same thing is true for the “Success” method.
1 2 3 4 5 6 7 8 9 10 11 12 |
public class AddRequestResult { public static IAddRequestResult Fail(string message) { throw new NotImplementedException(); } public static IAddRequestResult Success() { throw new NotImplementedException(); } } |
Let’s fix this. And by “this,” I mean the “Fail” method. Since that’s the method, that’s causing our test to fail. We’re not even touching “Success,” at least for now. The complete code for the class now looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class AddRequestResult : IAddRequestResult { public bool IsSuccess { get; private set; } public string ErrorMessage { get; private set; } public static IAddRequestResult Fail(string message) => new AddRequestResult(false, message); public static IAddRequestResult Success() { throw new NotImplementedException(); } private AddRequestResult(bool status, string errorMessage) { IsSuccess = status; ErrorMessage = errorMessage; } } |
As promised, we haven’t touched “Success.” Does the test pass now? Nope, not yet.
What is causing the failing? Easy. Our class doesn’t override “Equals.” Let’s take care of that.
1 2 3 4 5 6 7 8 |
public override bool Equals(object obj) { if (obj == null || GetType() != obj.GetType()) return false; var other = (AddRequestResult)obj; return IsSuccess == other.IsSuccess && ErrorMessage == other.ErrorMessage; } |
Now the test finally passes!
Let’s get to the next degenerate case. If some client tries to add a task with a due date already in the past, the operation should also fail. Let’s write the test case for that below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[Test] public void Execute_RequestWithPastDueDate_Fails() { IClock clock = new StoppedClock(new DateTimeOffset(2014, 3, 7, 9, 0, 0, TimeSpan.FromHours(-3))); ITaskRepository repo = new FakeTaskRepository(); var request = new AddTaskRequest { Title = "This won't work, I'm telling ya", DueDate = new DateTimeOffset(2014, 3, 6, 9, 0, 0, TimeSpan.FromHours(-3)) }; var sut = new AddTask(request, repo, clock); var expected = AddRequestResult.Fail("You're trying to add a task with a past due date."); Assert.AreEqual(expected, sut.Execute()); } |
At the start of the method, I configure my “StoppedClock” to always return the same date and time (March 7th, 2017, at 9 AM, with an offset of minus three hours from UTC) when asked for the current time. Then I proceed to instantiate a fake repository (same as the previous test). At last, I create a request, but this time instead of an empty string, I pass some text. Then, to populate the “DueDate” property, I pass a date that’s one day before the date my clock considers as being “current time.”
What should happen when I run the tests? The two of them pass!
That’s It For Today
That’s it for now. It might not feel like much, but we’ve definitely made progress toward the final solution by writing code in true TDD fashion. We’ve also picked up some good unit testing best practices that are listed below:
- Explicitly name the system under test variable as “SUT.” I’ve learned this with Mark Seeman aka ploeh.
- Don’t automatically name our fakes as “Fake[ImplementedInterface].” Sometimes you can come up with a more self-documenting name. For example, “StoppedClock” gives up the main characteristic of this fake. It qualifies its “fakeness,” so to speak.
- Follow Roy Osherove’s very useful naming convention for tests.
See you all next time!
Hi Carlos. Thanks for the articles. Do you think you can share the example’s source code? Thanks!