DEV Community

Cover image for The Ultimate Guide to Unit Testing in .NET (C#) Using xUnit and Moq (Without the Boring Examples)
Zied Rebhi
Zied Rebhi

Posted on

The Ultimate Guide to Unit Testing in .NET (C#) Using xUnit and Moq (Without the Boring Examples)

Alright, developers, gather ‘round. We’ve all been there, right? You’re looking for some unit testing examples, and what do you get? A classic sum function test. Really? The sum of two numbers? It’s like someone telling you, “Hey, you should totally learn to swim by jumping in the shallow end of the kiddie pool.” Ugh, no thanks!

But fear not, because in this article, we’re taking things up a notch. You’ll get real-world examples, not just the “add two numbers” stuff. We’re talking asynchronous methods, complex CQRS workflows, API controllers, and services that interact with databases. Sounds like a party? You bet! 🎉

So, buckle up as we dive deep into xUnit, Moq, and all the other goodies that make testing in C# both powerful and fun. By the end of this guide, you’ll be writing tests for real code in no time, and they’ll actually mean something. 🚀

Why xUnit and Moq?

You know that feeling when you’re trying to mock a database call and nothing seems to work? Or when you’re trying to test a service, but its dependencies are just too messy? Yeah, that’s where xUnit and Moq come in to save the day.

  • xUnit: It’s like the Swiss Army knife of unit testing. Simple, easy-to-use, and powerful enough to tackle anything.

  • Moq: The mock framework that makes dependencies easier to deal with. No more struggling with setup methods or breaking your code into a thousand pieces just to test one thing.

So, xUnit and Moq are like peanut butter and jelly. Together, they make testing smoother and tastier. 🍞🍇

When writing unit tests, you often need to isolate the code you’re testing from external dependencies like databases, APIs, or services. Moq allows you to create these “fake” objects, or mocks, so that you can focus on testing the logic of your code without worrying about the real external systems. With Moq, you can specify how these fake objects should behave (e.g., what they should return when a method is called), and verify that the methods are called correctly during the test. This makes testing more efficient and reliable.

Setting Up xUnit and Moq (It’s Simple, I Promise)

First, let’s get these bad boys installed so we can start writing tests:

No magic here. Just a quick setup, and we’re ready to roll.

1. Basic Service Testing (No, Not a Sum Function!)

Let’s start with something real. We’ll write a UserService that fetches user data from a repository. Why? Because fetching user data is something you actually do in real apps, not just summing numbers. 😜

Service: UserService

Test for UserService

Let’s test it. Mock the repository, and then check if the GetUserByIdAsync method actually returns a user (and doesn’t just return null—we're looking for something useful here, not just empty promises).

Takeaway: This is a real test for a service that retrieves data from a database. It’s not a “sum of two numbers” function, and you actually care about testing it. 🎯

2. Testing Asynchronous Methods (The Real Deal)

Let’s talk about async/await because real applications often deal with I/O-bound operations. You know, things like making HTTP calls or reading from a database. We need to test that this works correctly.

Service: PaymentService

Here, we’ll test a service that processes payments. It calls multiple external services to check user data, order details, and payment processing.

Tests for PaymentService

Now let’s test it, making sure everything is mocked and asynchronous behavior works as expected.

Takeaway: These tests are all about real-world async calls. They help us ensure the method interacts with multiple services and handles various scenarios correctly.

3. MediatR + CQRS (Let’s Make it Fancy)

You might have heard about CQRS (Command Query Responsibility Segregation) and MediatR. If not, let me explain: CQRS is about separating your reads and writes into different models, and MediatR helps you handle them. Let’s see how this works in real life.

Command: CreateOrderCommand

Test for CreateOrderCommandHandler

We’ll test that the command handler behaves correctly and sends the OrderCreatedEvent when a new order is created.

Takeaway: This is where things get advanced. We’re testing a CQRS command handler that uses MediatR to send events and execute commands. This is the real deal for complex applications.

4. Event-Driven Architecture (Publish-Subscribe Pattern)

In many modern systems, events drive a lot of the functionality. For instance, an order creation event might trigger a series of other actions, such as sending a confirmation email, updating the inventory, or notifying a user.

Let’s build an event-driven system using MediatR and test the subscription to an event.

Event: OrderCreatedEvent

Handler: OrderCreatedEventHandler

Test for OrderCreatedEventHandler

We’ll now write a test to ensure that the email service is triggered correctly when an order is created.

Takeaway: Event-driven systems are a common architecture, especially in microservices. This test ensures that the system reacts appropriately to events, like sending emails when an order is created.

5. Background Job Processing (Hangfire)

In many applications, you need to process tasks in the background. This is often done via background job processing systems like Hangfire or Quartz.NET.

Here’s how we can test a background job that processes a payment.

Background Job: ProcessPaymentJob

Test for ProcessPaymentJob

Let’s test the background job by ensuring that it calls the ProcessPaymentAsync method from the IPaymentService.

Takeaway: Background jobs are essential for handling long-running processes. This test ensures that background tasks trigger the necessary service methods.

6. Integrating with External APIs (Third-Party Service)

In real-world apps, you often need to interact with third-party APIs. Whether it’s sending a message through Twilio, fetching weather data from an external service, or processing payments through Stripe, you’ll need to test how your system interacts with these external systems.

External Service: WeatherService

Let’s assume we have a weather service that fetches the current temperature.

Test for WeatherService

To test this, we will mock the HttpClient using Moq and make sure that the service returns the expected response.

Takeaway: Testing integration with third-party services is crucial for external dependencies. By mocking the HTTP response, we ensure that our service works without actually calling the real API (and risking running out of API calls for the day!).

6. Testing API Controllers (With Dependency Injection)

In web applications, controllers often rely on dependency injection to get their services. Let’s look at testing an API controller that uses injected services.

Controller: OrderController

Test for OrderController

We’ll now test the controller method GetOrderById. We’ll mock the IOrderService to simulate database interaction.

Takeaway: When testing API controllers, mocking the injected services ensures that you test only the controller’s behavior, without depending on real databases or external services.

Conclusion: You’re Now Ready to Tackle Any Test!

Congratulations, my friend! 🎉 You’ve now seen advanced tests, covering everything from event-driven architectures, background jobs, third-party service integrations, to API controllers. With the power of xUnit, Moq, and MediatR, you’re now able to confidently test even the most complex systems.

The next time you hear someone say, “I don’t know how to test this,” you can confidently say, “I got you!” 🙌

Remember, in the world of testing, the more real your examples are, the more prepared you’ll be. So keep practicing, keep building, and make testing your superpower! 💪

Top comments (0)