In previous articles, we discussed writing tests using xUnit and writing tests for APIs . But when writing tests, in some cases we need to connect with the real database to read data from it or save records in it. If we use a fixed database, the values in the database will change every time the tests are run, and if someone else works with the database and changes its data, there is a high probability that the tests will not run successfully.

The testcontainer  library allows us to create a test database in Docker before running the tests, import the initial data, run migrations, and then run the tests and finally delete the created database. This library runs the database we need in Docker and after running the test, the container is deleted.
In this article, we will implement a test example for Redis. First, we create a class called RedisCacheRepository to add and receive information from Redis:

public class RedisCacheRepository : ICacheRepository
{
    private readonly IDatabase _datebase;
    public RedisCacheRepository(IConnectionMultiplexer connectionMultiplexer)
    {
        _datebase = connectionMultiplexer.GetDatabase();
    }

    public async Task<T> GetAsync<T>(string key)
    {
        var redisValue = await _datebase.StringGetAsync(key);

        if (string.IsNullOrWhiteSpace(redisValue))
            return default;

        return JsonConvert.DeserializeObject<T>(redisValue);
    }

    public async Task SetAsync<T>(string key, T value,  TimeSpan expireTime)
    {
        var redisValue = JsonConvert.SerializeObject(value);

        await _datebase.StringSetAsync(key, redisValue, expireTime);
    }
}

Next, we need to create a class that implements IAsyncLifeTime and in the DisposeAsync and InitializeAsync methods, enter the commands related to running the container and deleting it. In this class, you can also create the connection related to Redis and use that connection in the test classes:

public class RedisInitializer : IAsyncLifetime
{
    private readonly RedisTestcontainer _redisTestcontainer;

    public IConnectionMultiplexer RedisConnection { get; private set; }
    public RedisInitializer()
    {
        _redisTestcontainer = new TestcontainersBuilder<RedisTestcontainer>()
           .WithDatabase(new RedisTestcontainerConfiguration
           {
               Port = 6379
           })
           .WithImage("redis:5.0.14")
           .WithCleanUp(true)
           .WithExposedPort(6379)
           .WithPortBinding(6379, true)
           .Build();
    }
    public async Task DisposeAsync()
    {
        await _redisTestcontainer.StopAsync();
    }

    public async Task InitializeAsync()
    {
        await _redisTestcontainer.StartAsync();

        RedisConnection = ConnectionMultiplexer
            .Connect(_redisTestcontainer.ConnectionString);
    }
}

After the StartAsync method related to RedisTestcontainer is executed, we initialize the RedisConnection property, which is of the IConnectionMultiplexer type, to use it in the tests.
Now we can implement the tests related to RedisCacheRepository:

[Collection("Sequential")]
public class RedisCacheRepositoryTest : IClassFixture<RedisInitializer>
{
    private readonly ICacheRepository _cacheRepository;

    public RedisCacheRepositoryTest(RedisInitializer redisInitializer)
    {
        _cacheRepository = new RedisCacheRepository(redisInitializer.RedisConnection);
    }

    [Fact]
    public async Task When_CacheKeyIsNotExist_Then_ForReferenceTypesNullShouldReturned()
    {
        var invalidCacheKey = "invalidCacheKey";

        var result = await _cacheRepository.GetAsync<UserModel>(invalidCacheKey);

        Assert.Null(result);
    }

    [Fact]
    public async Task When_CacheTimeIsExpired_Then_CacheRepositoryShouldReturnDefaultValue()
    {
        var cacheKey = "Test";

        var data = 10;

        var expireTime = TimeSpan.FromSeconds(10);

        await _cacheRepository.SetAsync(cacheKey, data, expireTime);

        var cacheValue = await _cacheRepository.GetAsync<int>(cacheKey);

        Assert.Equal(data, cacheValue);

        await Task.Delay(expireTime);// wait to expire cache

        cacheValue = await _cacheRepository.GetAsync<int>(cacheKey);

        Assert.Equal(0, cacheValue);
    }
}

The first test is related to receiving the default value for the reference types, which will return a null value if there is no data in Redis, and the next test is related to the deletion of Redis data after the expiration time.
Now, if you run the tests, the Redis container must be run before running the tests and after running the tests, the created containers should be deleted.
Using the testcontainer library, you can create tests for these databases .

Powered by Froala Editor

Comments