germi February 2016

Unit Testing Cache Behaviour of Akavache with TestScheduler

So I'm trying to test caching behaviour in an app that's using Akavache. My test looks like this:

using Akavache;
using Microsoft.Reactive.Testing;
using Moq;
using NUnit.Framework;
using ReactiveUI.Testing;
using System;
using System.Threading.Tasks;

[TestFixture]
public class CacheFixture
{
    [Test]
    public async Task CachingTest()
    {
        var scheduler = new TestScheduler();
        // replacing the TestScheduler with the scheduler below works
        // var scheduler = CurrentThreadScheduler.Instance;
        var cache = new InMemoryBlobCache(scheduler);

        var someApi = new Mock<ISomeApi>();
        someApi.Setup(s => s.GetSomeStrings())
            .Returns(Task.FromResult("helloworld")).Verifiable();
        var apiWrapper = new SomeApiWrapper(someApi.Object, cache,
            TimeSpan.FromSeconds(10));

        var string1 = await apiWrapper.GetSomeStrings();
        someApi.Verify(s => s.GetSomeStrings(), Times.Once());
        StringAssert.AreEqualIgnoringCase("helloworld", string1);

        scheduler.AdvanceToMs(5000);
        // without the TestScheduler, I'd have to 'wait' here
        // await Task.Delay(5000);

        var string2 = await apiWrapper.GetSomeStrings();
        someApi.Verify(s => s.GetSomeStrings(), Times.Once());
        StringAssert.AreEqualIgnoringCase("helloworld", string2);
    }
}

The SomeApiWrapper uses an internal api (mocked with new Mock<ISomeApi>()) that - for simplicity's sake - just returns a string. The problem now is that the second string is never returned. The SomeApiWrapper class that handles the caching looks like this:

using Akavache;
using System;
using System.Reactive.Linq;
using System.Threading.Tasks;

public class SomeApiWrapper
{
    private IBlobCache Cache;
    private ISomeApi Api;
    private TimeSpan Timeout;

    public SomeApiWrapper(ISomeApi api, IBlobCache cache, TimeSpan        

Answers


Lee Campbell February 2016

This is a fairly common problem when bouncing between the Task and the IObservable paradigms. It is further exacerbated by trying to wait before moving forward in the tests.

The key problem is that you are blocking* here

return await cachedStrings.FirstOrDefaultAsync();

I say blocking in the sense that the code can not continue to process until this statement yields.

On the first run the cache can not find the key, so it executes your DoGetStrings. The issue surfaces on the second run, where the cache is populated. This time (I guess) the fetching of the cached data is scheduled. You need to invoke the request, observe the sequence, then pump the scheduler.

The corrected code is here (but requires some API changes)

[TestFixture]
public class CacheFixture
{
    [Test]
    public async Task CachingTest()
    {
        var testScheduler = new TestScheduler();
        var cache = new InMemoryBlobCache(testScheduler);
        var cacheTimeout = TimeSpan.FromSeconds(10);

        var someApi = new Mock<ISomeApi>();
        someApi.Setup(s => s.GetSomeStrings())
            .Returns(Task.FromResult("helloworld")).Verifiable();

        var apiWrapper = new SomeApiWrapper(someApi.Object, cache, cacheTimeout);

        var string1 = await apiWrapper.GetSomeStrings();
        someApi.Verify(s => s.GetSomeStrings(), Times.Once());
        StringAssert.AreEqualIgnoringCase("helloworld", string1);

        testScheduler.AdvanceToMs(5000);

        var observer = testScheduler.CreateObserver<string>();
        apiWrapper.GetSomeStrings().Subscribe(observer);
        testScheduler.AdvanceByMs(cacheTimeout.TotalMilliseconds);

        someApi.Verify(s => s.GetSomeStrings(), Times.Once());


        StringAssert.AreEqualIgnoringCase("helloworld", observer.Messages[0].Value.Value);
    }
}

public interface ISomeApi
{
    Task<string> GetSomeS 

Post Status

Asked in February 2016
Viewed 1,189 times
Voted 8
Answered 1 times

Search




Leave an answer