Logo

dev-resources.site

for different kinds of informations.

Test with Dummy and Stub

Published at
12/27/2024
Categories
csharp
tdd
tutorial
newbie
Author
_cds_
Categories
4 categories in total
csharp
open
tdd
open
tutorial
open
newbie
open
Author
5 person written this
_cds_
open
Test with Dummy and Stub

Let’s put theory into practice and start testing! In this article, we’ll focus on two types of Test Doubles — Dummy and Stub — to fully test our WeatherForecastController. Ready? Let’s dive in!

Dummy

As we can see from the previous article, the WeatherForecastController has two dependencies: the IWeatherServiceand the logger.

Although logging is important, we decided that "we don't care" about the logging part of the code. With this in mind, the logger becomes a good candidate for a Dummy since it is only needed for the Controller initialization, and we can't pass null to the constructor otherwise, at some point during the test, we would get an exception when the logger is called.

As you may remember from the first article of this series, a Dummy is an object that does nothing. The implementation of a Dummy for ILogger<WeatherForecastController> could look like this:

internal class DummyLogger<T> : ILogger<T>
{
    public IDisposable? BeginScope<TState>(TState state) where TState : notnull
        => null;

    public bool IsEnabled(LogLevel logLevel)
        => false;

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
    {
    }
}
Enter fullscreen mode Exit fullscreen mode

Pretty easy, right? Just let Visual Studio implement the interface and return some default values where needed.

Can you spot the pattern? This is an implementation of the Null Object Pattern, which ensures that we don’t run into null reference errors during testing while keeping the code clean and focused on what we’re testing.


Tip: You can have a null logger for free using the NullLogger<T> in the Microsoft.Extensions.Logging.Abstractions namespace.


Stub

Now we need an implementation of the IWeatherService:

public interface IWeatherService
{
    IEnumerable<WeatherForecast> GetByCity(string city);
}
Enter fullscreen mode Exit fullscreen mode

with fixed response to predefined input.
Here we go:

internal class StubWeatherService : IWeatherService
{
    private readonly List<WeatherForecast> _weatherForecast = 
        [
            new WeatherForecast
            {
                Date = DateOnly.FromDateTime(DateTime.Now),
                TemperatureC = 20,
                Summary = "Test Summary"
            }
        ];

    public IEnumerable<WeatherForecast> GetByCity(string city)
        => string.Equals(city, "Rome") ? _weatherForecast : Enumerable.Empty<WeatherForecast>();
}
Enter fullscreen mode Exit fullscreen mode

With this implementation, we are sure that we can get a non-empty list when we pass "Rome" as the input to the GetByCity method.

Now we can create the WeatherForecastController using the Dummy logger and the Stub service. With this in place, unit testing the GET method becomes straightforward.

Tests

As a reminder, this is the method we want to test:

[HttpGet("{city}")]
public IActionResult Get(string city)
{
    var data = _weatherService.GetByCity(city);
    if (data.Any())
        return Ok(data);

    _logger.LogInformation("No data found for {City}", city);

    return NoContent();
}
Enter fullscreen mode Exit fullscreen mode

We know that "Rome" is the input for the GetByCity method to return some results, and we can make this information explicit in a variable in our test class, which looks like this:

public class TestsWithStub
{
    private readonly WeatherForecastController _sut;
    private readonly string _cityWithData = "Rome";

    public TestsWithStub()
    {
        _sut = new WeatherForecastController(
            weatherService: new StubWeatherService(),
            logger: new DummyLogger<WeatherForecastController>());
    }
}
Enter fullscreen mode Exit fullscreen mode

Following the code of the Controller, we want our first test to assert that we can get an Ok response with weather data when they are available, and we know they are for the input "Rome". Here's the code using XUnit:

[Fact]
public void Get_ReturnOk_When_Data_Exists()
{
    IActionResult actual = _sut.Get(_cityWithData);

    var okResult = Assert.IsType<OkObjectResult>(actual);
    var forecasts = Assert.IsAssignableFrom<IEnumerable<WeatherForecast>>(okResult.Value);
    Assert.NotEmpty(forecasts);
}
Enter fullscreen mode Exit fullscreen mode

We use Assert.IsType<OkObjectResult> to ensure the response type is correct, and Assert.IsAssignableFrom<IEnumerable<WeatherForecast>> to validate the returned object structure.

The second and last test will assert for a NoContent response when no data is available:

[Fact]
public void Get_ReturnNoContent_When_Data_NotExists()
{
    IActionResult actual = _sut.Get("Paris");

    Assert.IsType<NoContentResult>(actual);
}
Enter fullscreen mode Exit fullscreen mode

We have fully tested our WeatherForecastController!

What’s Next?

In the next article, we'll evolve the StubWeatherService, making it a Spy and exploring what we can do with it.

Stay tuned!

tdd Article's
30 articles in total
Favicon
Test in Isolation
Favicon
Why Test Driven Development
Favicon
How to start with test driven development (TDD)
Favicon
What we will test
Favicon
How Test-Driven Development (TDD) Enhances Code Refactoring and Maintains Software Quality
Favicon
Why should I care about Quality? I'm a developer!
Favicon
TDD with spring-boot: A struggle of an experienced developer
Favicon
Test with Spy and Mock
Favicon
Test-Driven Development (TDD) with Bun Test
Favicon
Test with Dummy and Stub
Favicon
Modern Test Pyramid
Favicon
Some Essential Coding Practices Every Experienced Developer Recommends
Favicon
Not everything is a Mock, let's explore Test Doubles
Favicon
When does the TDD approach make sense?
Favicon
Go-DOM - 1st major milestone
Favicon
Stop saying that Test-Driven Development is just a testing methodology!
Favicon
Minitest Advantages: Simple Testing for Rails Projects
Favicon
Comprehensive Testing in .NET 8: Using Moq and In-Memory Databases
Favicon
From PHPUnit to Go: Data-Driven Unit Testing for Go Developers
Favicon
YAGNI For Types
Favicon
Go-DOM - A headless browser written in Go.
Favicon
Test-Driven Development: A Comprehensive Guide
Favicon
There's no place for Test-Driven Development (TDD)
Favicon
EasyTdd 0.5.0: Streamlining Mocking with Incremental FluentMock
Favicon
Learn TDD with Ruby - Loops, Blocks and Strings
Favicon
Shaping the state of Test-Driven Development
Favicon
Test-Driven Development For Analytics Engineering
Favicon
Test Driven Api Development With Cypress
Favicon
Guide to Building a Complete Blog App with Django using TDD methodology and PostgreSQL database: Installation and Setup
Favicon
Mock server

Featured ones: