Rate Limiting a .NET Core Application using the Sliding Window Algorithm — Part 2, Validation with Unit Tests
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,
GetElapsedTimeInTicksas the name suggests, converts milliseconds to time in ticks
GetMinimumElapsedTimeInTickscalculates how much time must elapse for a request to conform given the rate limit and the current request count*
requestLimitrequests, mocking the time returned by the
ITimestampimplementation with a value for the time elapsed that starts with
timeElapsedTicksand increments by
incrementTicksfor 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
SlidingWindowinstance passing in arbitrary values for the rate limit and our mock
- We saturate the window with as many requests as the value of
requestLimitvia 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
SlidingWindowand saturate the initial window, assigning
timeElapsedTicksto the return of
- At this point, the value of
timeElapsedTicksplaces 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
- 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
- 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
SlidingWindowinstance and saturate the initial window, assigning
timeElapsedTicksto the return value of
- Increment the the value for
timeElapsedTicksby two window lengths
- Saturate the window again, using the value of
timeElapsedTicksas the starting value returned by
ITimestamp.GetTimestampand verifying that the new, current window can be saturated with as many requests as the value of
Running the Tests
From the image below, you can see that our unit tests run successfully — or, at least on my local they do 😜.
This concludes part two of four in the series 😌. I hope this series of articles has been helpful to you. As I have said in the previous article, if you have any feedback — please, let me know!