0

I have an API endpoint I'm testing using Ruby. I have 8 tests that look like the following:

it "must rate limit on this api" do

  body = {
    ..
  }

  # Current limits assigned to this api endpoint are 5 requests every 5 minutes.
  # So this test will make 5 calls in short succession to trigger the rate limiter
  with_logging_suppressed do
    5.times do
      post '/api/endpoint/', body.to_json
      last_response.status.must_equal 200
    end

    post '/api/endpoint/', body.to_json
    last_response.status.must_equal 429
    last_response.body.include? "You are doing this too often"
  end
end

When you POST to /api/endpoint/, it spawns a RateLimit class which contains a validate_rate? method and it uses an instance variable of @timestamp which is an array of timestamps of when the API has been called.

RATE_LIMIT = RateLimit.new(5, 5.minutes)
post '/api/endpoint/' do
  if RATE_LIMIT.validate_rate?
    ...
  else
    throw Error
  end
end

RateLimit would contain the following @timestamp instance variable with the following sample of timestamps which validate_rate? would compare against.

@timestamp = [2018-04-16 19:17:48 -0400, 2018-04-16 19:17:49: -0400, 2018-04-16 19:17:58 -0400]

However, prior to each test run, I MUST clear out the @timestamp array or the array will contain timestamps from previous tests, which of course ruins the test results

So I attempted to try to set the @timestamp array to a new array prior to each test run in the form of a before block.

before do
  RateLimit.instance_variable_set(:@timestamp, [])
end

The issue I am currently running into right now is that because the RateLimit instance isn't specifically created in this test, when I call post /api/endpoint/, I cannot seem to set the @timestamp variable if this makes sense. It does set a @timestamp to [], but it's not setting the specific instance that was created when I am running the actual test

I want to keep @timestamp private, so I do not want to create a method that lets me get or set this private variable, so my only choice seems to be instance_variable_set.

theGreenCabbage
  • 5,197
  • 19
  • 79
  • 169
  • It seems like a bad design to depend on class instance variables like that, but even so, if that code runs it should assign. The question is, did it run at the right time? – tadman Apr 16 '18 at 23:29
  • I've ran a debugger in the `RateLimit` class to use `instance_variable_get` on `@timestamp`, and the results of the debugger in the test file vs the `RateLimit` class returns different results. The test file returns `[]` for `@timestamp` for any time I stop the debugger, while if I call the debugger in `RateLimit`, it would return the correct `@timestamp` object with timestamps inside. This is what leads me to think I'm not getting the right instance – theGreenCabbage Apr 16 '18 at 23:34
  • Is `RateLimit` a model? Does it get reloaded between tests? – tadman Apr 17 '18 at 00:38
  • It's not a model – theGreenCabbage Apr 17 '18 at 01:02
  • RateLimit.new.instance_variable_set(:@timestamp, []) should work as expected – andriy-baran Apr 17 '18 at 08:14
  • @andriy-baran `RateLimit.new.instance_variable_set(:@timestamp, [])` does not work, it tells me I need to set the 2 inputs `RateLimit.new(..,..,..)` – theGreenCabbage Apr 18 '18 at 21:06

1 Answers1

-1

You should stub another method:

before do
  allow_any_instance_of(RateLimit).to receive(validate_rate?).and_return(true)
end

Then test RateLimit.new in separate test examples

andriy-baran
  • 620
  • 4
  • 16