NDepend Blog

Improve your .NET code quality with NDepend

Continuing Our Clean Architecture Example in C Sharp

Continuing Our Clean Architecture Example in C#

June 19, 2018 6 minutes read

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:

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:

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:

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:

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:

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.

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:

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.

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:

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!

Comments:

  1. Daniel Lacayo says:

    Hi Carlos. Thanks for the articles. Do you think you can share the example’s source code? Thanks!

Comments are closed.