
Welcome! No more tests for a Calculator class. Let's learn unit testing best practices by refactoring real tests. Directly in Visual Studio. No boring slides, I promise.
Writing unit tests should be like telling a good story, with enough details to keep the story interesting but not too many to make it boring.
Use builder methods to reduce the "noise" and maintain the right level of detail in your tests.
This AccountController sends a welcome email to new clients after they sign up. That email contains a contact-us and other emails. This test validates that all those emails are present when creating an AccountController.
To keep reducing the noise in your tests, when working with the IOptions interface, use Options.Create() instead of a fake object with Moq.
Make the scenario under test obvious by initializing properties to the appropriate values. If you say something in a test name, make it easy to find inside the test.
Duplication is the root of all evil, even inside unit tests. To test the same method with multiple input values, use parameterized tests.
This PaymentRequestValidator, built using FluentValidation, checks that credit card expiration dates don't contain invalid months.
FluentValidation has helper methods for testing. Use them instead of directly asserting something on the result of TestValidate().
Instead of using loops to test the same method with various input values, use parameterized tests to avoid duplication.
When we can't use parameterized tests, our only option is to split a single test into multiple ones. Let's keep a single concern per test.
This PaymentProxy is a "client" service is responsible for taking payments from other modules within the system. It has a method called Capture() that expects a Deposit object.
To write more maintainable tests, avoid checking for exact matches of exception messages in your assertions. Any change in the punctuation, spelling, or structure of exception messages will make your tests fail.
Use object mothers to create test data. Using a dictionary and parameterized tests is hacky.
This ImportGuestsService validates that an uploaded file contains the required data.
Now it's your turn: your task is to refactor a test related to guest uploads. Pause the video and try to do it yourself. If you're stuck, there are some clues and a solution at the end.
Before refactoring tests that involve fakes, let's use a simple example to review what fakes are and differentiate between the two main types: stubs and mocks.
It's easy to create fakes by hand, but mocking libraries make things even easier. Let's explore one well-known mocking library: Moq. We're using Moq in the next lessons.
Make your test easier to follow by visually separating the Arrange, Act, and Assert parts of your tests. Instead of using vague terms, like "happy path" or "success," clearly state the scenario under test and the expected result.
This PayoutService feeds a payout report in Excel. It relies on an external payment gateway, such as Stripe, to display all transactions associated with payouts within a specified date range.
When dealing with multiple dependencies in the class being tested, using builders becomes tedious. Let's cover a simple alternative: TypeBuilder.
Remember that stubs are not the same as mocks. Let's stop asserting on stubs. When we do that, we're testing the mocking library, not our code.
Use simple test values to make your tests easier to follow. Do not overcomplicate things.
This DictionaryExtensions class contains a method that merges dictionaries.
It's your turn: refactor another test that involves merging dictionaries. Pause the video and try to do it yourself. If you're stuck, there are some clues and a solution at the end.
Avoid exposing private methods to test them directly. Instead, use public methods to test internal functionality. This is THE most common mistake when writing unit tests.
This SendEmailCommandHandler uses a repository to store emails, which are later sent behind the scenes.
Let's keep reducing the noise in our tests. This time, let's write a custom Verify() method for a mock.
Now it's your turn: Refactor a test that stores an email with duplicate addresses. Pause the video and try to do it yourself. If you're stuck, there are some clues.
Often, we bury assumptions inside builders, only to rely on those hidden assumptions throughout the rest of our tests. This practice makes our tests harder to follow. Let's make our assumptions explicit by creating a small domain-specific language in our tests.
This UpdateStatusCommandHandler uses webhooks or delivery notifications from a third-party email provider, such as Amazon SES, to update the statuses of stored emails.
Now it's your turn again: Refactor another test that updates an email status from a webhook. Pause the video and try to do it yourself. If you're stuck, there are some clues and a solution at the end.
If you have reached this point, congrats. I hope you have learned how to write readable and maintainable unit tests in C#. Let's summarize what we covered in this course.
Have you learned the basics of unit testing in C#, but you still struggle with writing tests that are easy to read and maintain?
Have you seen too many online tutorials testing a Calculator class, but you want to learn how to test real code?
Code with dependencies, validations, and edge cases.
Code that looks like the code you find every day at work.
That's why I created this course.
No more unit testing the Add method of a Calculator class.
In this course, let's refactor some real unit tests to make them more readable and maintainable using proven principles and methods.
Although I changed names, classes, and methods to avoid disclosing code from past clients and employers, these are the tests I had to work with as a software engineer.
Who Is This For?
This course is for C# developers who:
Know what MSTest, Moq, and AAA mean.
Want to write readable and maintainable unit tests.
Are tired of boring, unrealistic tutorials and want to learn from real code examples.
What You’ll Learn
By the end of this course, you’ll be able to:
Simplify tests with builder methods.
Write maintainable tests easily with parameterized values.
Make tests clear and easy to follow with good names and test values.
Have more readable tests with domain-specific assertion methods.
Mastering these skills will help you to write tests easy to follow, easy to maintain, and easy to trust.
The next time you open one of your tests, you’ll know exactly what’s going on.
Important Note:
This is not an introductory course. We won't cover how to write your first unit tests.
If you already know what mocks are and when to use them, you're ready for this.
What's Inside
In this course, you will get access to:
8 lessons covering different best practices for unit testing.
10+ real unit tests that need refactoring.
4 hands-on exercises to apply what you've learned.
1 checklist summarizing all best practices.
Ready to master unit testing with real-world examples? Fire up your Visual Studio and let's start writing clean, readable, and maintainable unit tests—the kind of tests your future self and your teammates will thank you for.
See you in Lesson 1!