Reactive Extensions. Api client with Cache-Aside & Refresh-Ahead strategy. Part 2.
Published on:
Hi,
In the previous post I told you how to implement the Reactive Api client with caching. Well, to prove that our code works we might want to have some unit tests.
To do so let’s create a new project (Class Library), add a reference to the main project, and install few NuGet packages.
Rx-Main, Rx-Testing, xUnit.Net and Moq.
Install-Package Rx-Main
Install-Package Rx-Testing
Install-Package xunit
Install-Package Moq
Then we’ll create a class RxGitHubClientTest, inherited from ReactiveTest.
ReactiveTest
is a base class which is shipped with Rx-Testing
. It implements some infrastructure for our tests.
I’m not going to post all the tests here, you can find them on GitHub (the link is in the end of the post).
Setup
As an example I’m going to test this scenario: If the cache is empty GitHubClient should download them, persist in a cache, and call OnNext
and OnCompleted
.
To do that we’ll need to mock IHttpClient
and IRatingCache
. Another class that we’ll use is TestScheduler from Rx-Test
package.
TestScheduler implements IScheduler interface and can be substituted instead of platform dependent instance of scheduler. It allows us literally to control the time and execute asynchronous code step by step. If you’d like to know more details I recommend to read a good post “Testing Rx Queries using Virtual Time Scheduling”.
Now our test set up looks like this:
public RxGitHubClientTest()
{
Model = new RatingModel
{
Rating = 10,
LastModified = new DateTime(2015, 07, 10, 1, 1, 1, 0),
Id = UserName
};
Cache = new Mock<IRatingCache>();
Scheduler = new TestScheduler();
HttpClient = new Mock<IHttpClient>();
}
Arrange
First of all we need to set up Mocks behavior: the cache is empty, http client can download and parse the request.
Cache.Setup(c => c.HasCached(It.IsAny<string>()))
.Returns(false);
Cache.Setup(cache => cache.GetCachedItem(UserName))
.Returns((RatingModel) null);
HttpClient.Setup(http => http.Get(UserName))
.ReturnsAsync(new RatingResponse
{
Data = Model,
IsSuccessful = true
});
var client = CreateClient();
OnNext
and OnCompleted
events.
var expected = Scheduler
.CreateHotObservable(OnNext(2, Model), OnCompleted<RatingModel>(2));
OnNext(2, Model)
is a method which is declared in ReactiveTest
.
It has a following signature:
static Recorded<Notification<T>> OnNext<T>(long ticks, T value)
OnNext
method was called with Model parameter. The Magic Number “2” is nothing more than the time in ticks. It’s not a real time, it’s the TestScheduler’s virtual time. It’s not the most elegant solution though. The TestScheduler is created at tick number zero, on the tick number one we subscribe on events and on the tick two we’ll get the first event.
Act
var results = Scheduler
.Start(() => client.GetRatingForUser(UserName), 0, 1, 10);
TestScheduler
, which will be initialized in the zero tick, then it will subscribe on client.GetRatingForUser(UserName)
on the first tick. The last parameter is the virtual time on which the subscriber should be disposed. We can ignore this one for now.
And finally the last step.
Assert
ReactiveAssert.AreElementsEqual(expected.Messages, results.Messages);
Cache.Verify(cache => cache.Put(Model), Times.Once);
This test could have been splitted into two parts, to follow the best practices (to have one assert per test method), but I wanted to show that all the regular approaches are valid in Rx-world.
Ok, now we have a green bar.
Source code
You can find the source code on GitHub
Conclusion
I mentioned Windows Phone development in my article, however the code is built for .NET 4.5. I did that on purpose, to let everybody checkout and build the solution even if one has no WP SDK installed locally. It’s easy to convert the project to PCL or WP8/8.1 assembly. All of them are supported by Rx.
Links and sources
- Testing Rx
- Testing Rx Queries using Virtual Time Scheduling
- Testing and Debugging Observable Sequences