Rate Limiting a .NET Core Application using the Sliding Window Algorithm — Part 2, Validation with Unit Tests
Tags: Algorithms, Unit Testing, Networking, Csharp, Dotnet Core

In the first article of this series, we implemented a rate limiter in .NET Core using the sliding window algorithm with the intention that it will be used by an application to self-limit its requests to an API. Before we can put our implementation into practice, we first need to validate that it functions as intended.

In this article, we will discuss how to validate our rate limiter with unit tests. We will first setup the test class and several helper methods that can be reused throughout our unit tests. We will then turn our attention towards the unit tests, examining each based on the scenario that they assess; this is done similarly to how we implemented the rate limiter in the previous article. The unit tests will be written using xUnit and Moq.

Setting up the Test Class

Setup of our test class, SlidingWindowTests is relatively simple, requiring only a single mock implementation and three helper methods. The setup for the SlidingWindowTests class is displayed below,

As you can see from the Github gist, we need only mock a single implementation: the ITimestamp interface. We mock ITimestamp so that we can precisely control what values of the time are returned from calls to ITimestamp.GetTimestamp() made by the SlidingWindow class (as was alluded to in the previous article).

We have added several helper methods to our test class to aid with code reuse,

GetElapsedTimeInTicks as the name suggests, converts milliseconds to time in ticks

as the name suggests, converts milliseconds to time in ticks GetMinimumElapsedTimeInTicks calculates how much time must elapse for a request to conform given the rate limit and the current request count*

calculates how much time must elapse for a request to conform given the rate limit and the current request count* SaturateWindow saturates the SlidingWindow object by requestLimit requests, mocking the time returned by the ITimestamp implementation with a value for the time elapsed that starts with timeElapsedTicks and increments by incrementTicks for every iteration. The method assumes that each request conforms up to the limit and asserts this using the test framework

A rudimentary unit test is also included in the code excerpt above; its responsibility is to verify that the SlidingWindow class is performing validation on its constructor inputs.

*The equation for GetMinimumElapsedTimeInTicks is derived from the equation for calculating the approximate request count by assuming the previously tracked window reached the rate limit and then solving for the elapsed time.

Unit Tests by Scenario

Our unit tests can be divided into three scenarios — each assessing how the implementation responds under certain conditions:

When an incoming request is received, but there is no previous window

When an incoming request is received, and there is a previous window

When the time between the incoming request and the start of the former, current window meets or exceeds two or more window lengths

Scenario #1: A previous window does not exist when checking the conformity of an incoming request

As the title states, we need to assess how the rate limiter responds when it is in a state where there is not a previous window. This can occur in two situations,

The current window being tracked is the first seen by the rate limiter

There has been a significant amount time between the start of the former, current window and the incoming request (seen in the last scenario)

We will address the former situation in this sub-section and save the latter for the third. When this situation occurs, we expect the rate limiter to allow all requests up to the request limit (i.e. if the request limit is 50, then 50 requests will conform/be allowed). The unit test for this scenario is displayed below,

The unit test above is rather simple,

We create a SlidingWindow instance passing in arbitrary values for the rate limit and our mock ITimestamp implementation

instance passing in arbitrary values for the rate limit and our mock implementation We saturate the window with as many requests as the value of requestLimit via a call to SaturateWindow() — which also acts to verify that each request up to the limit is considered to be conforming by the rate limiter

Notice that we provide 0 for the input value of incrementTicks . We do this because the elapsed time would have no impact: a previous window cannot be used to calculate the weighted request count and the request limit for the current window has not been met.

Scenario #2: A previous window exists when checking the conformity of an incoming request

The second scenario is somewhat more involved than the previous. In this scenario, the rate limiter has a previous window to reference so we expect it to impact checks to the conformity of incoming requests. We can assess our rate limiter’s response in this scenario through the following unit test,

The unit test above is certainly a larger block of code, but is no more complex than the first,

We create an instance of SlidingWindow and saturate the initial window, assigning timeElapsedTicks to the return of SaturateWindow()

and saturate the initial window, assigning to the return of At this point, the value of timeElapsedTicks places us at the start of the next window, so we calculate the minimum time that needs to elapse before the next request will conform

places us at the start of the next window, so we calculate the minimum time that needs to elapse before the next request will conform We setup the next call to ITimestamp.GetTimestamp() to return a timestamp that is only half of the minimum elapsed time calculated in the previous step and then assert that the next request does not conform

to return a timestamp that is only half of the minimum elapsed time calculated in the previous step and then assert that the next request We do the same thing as the previous step, but setup ITimestamp.GetTimestamp() to return a timestamp that now equals the minimum elapsed and assert that the next request does conform

to return a timestamp that now equals the minimum elapsed and assert that the next request Lastly, we verify that the next request does not conform since we have not advanced the time returned

You may have noticed that we have setup this unit test as a theory. This is not done to test equivalence classes per say, but to give some validation to our calculation of the minimum elapsed time with varying values for the rate limit.

Scenario #3: The timestamp of the incoming request exceeds the current window by two window lengths

The third and final scenario verifies that an incoming request is unencumbered by the former, current window when there has been a significant amount of time between the start of the window and the incoming request (two window lengths to be exact). The corresponding unit test is rather simple, constructed similarly to the unit test of the first scenario:

To validate the rate limiter for this scenario, we take the following steps in our unit test above,

Create a SlidingWindow instance and saturate the initial window, assigning timeElapsedTicks to the return value of SaturateWindow()

instance and saturate the initial window, assigning to the return value of Increment the the value for timeElapsedTicks by two window lengths

by two window lengths Saturate the window again, using the value of timeElapsedTicks as the starting value returned by ITimestamp.GetTimestamp and verifying that the new, current window can be saturated with as many requests as the value of requestLimit

Running the Tests

From the image below, you can see that our unit tests run successfully — or, at least on my local they do 😜.