Skip to content

Commit

Permalink
Ensure stubs are called exactly once
Browse files Browse the repository at this point in the history
Ensure that the stubs are called exactly once -- that is, that the
same stub is not used several times, potentially masking a bug where
several transmissions happen for the same events, and that no stubs
are declared which are left unused.

The strange way in which it is implemented is due to certain quirks
of how the `webmock` gem works. I could not find a way to have a
stub automatically disable itself when called, similar to what the
`nock` Node.js package provides -- so I implemented this by passing
a number of request bodies to expect to respond to when declaring
the mock, and erroring if more requests are made than the request
bodies provided.

That, however, does not cover the scenario where the stub is not
called at all. Somewhat bafflingly, the `have_been_requested` matcher
does not check a counter that has been incremented for each request.
Instead, it re-runs the requests it has stored by the stubs _again_,
and counts how many match. Given the implementation described above,
where the stub is exhausting a list of request bodies as it goes,
the matcher will fail when attempting to go a second time around.

So instead, we increment a counter when a request is processed
(without said request having exhausted the matcher) and we return
that counter from the stub. The tests store these counters in a
`let` binding, and an `after` block checks that each stored counter
has been increased to one. This is done in an `after` block not
only for convenience, but because some tests are mocking a call
that will only be performed in that same `after` block, when the
scheduler is stopped.
  • Loading branch information
unflxw committed Sep 10, 2024
1 parent 0b41476 commit 1971bbe
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 118 deletions.
63 changes: 29 additions & 34 deletions spec/lib/appsignal/check_in/cron_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@
let(:config) { project_fixture_config }
let(:cron_checkin) { described_class.new(:identifier => "cron-checkin-name") }
let(:scheduler) { Appsignal::CheckIn.scheduler }
let(:stubs) { [] }

before do
start_agent(
:options => appsignal_options,
:internal_logger => test_logger(log_stream)
)
end
after { stop_scheduler }

def stop_scheduler
after do
scheduler.stop

stubs.each do |stub|
expect(stub.count).to eq(1)
end
end

describe "when Appsignal is not active" do
Expand All @@ -26,7 +30,7 @@ def stop_scheduler

cron_checkin.start
cron_checkin.finish
stop_scheduler
scheduler.stop

expect(logs).to contains_log(
:debug,
Expand All @@ -48,24 +52,25 @@ def stop_scheduler

expect(logs).to contains_log(
:debug,
"Cannot transmit cron check-in `cron-checkin-name` start event"
/Cannot transmit cron check-in `cron-checkin-name` start event .+: AppSignal is stopped/
)
expect(logs).to contains_log(
:debug,
"Cannot transmit cron check-in `cron-checkin-name` finish event"
/Cannot transmit cron check-in `cron-checkin-name` finish event .+: AppSignal is stopped/
)

scheduler.stop
end
end

describe "#start" do
it "sends a cron check-in start" do
cron_checkin.start

stub_check_in_request(
stubs << stub_cron_check_in_request(
:events => [
"identifier" => "cron-checkin-name",
"kind" => "start",
"check_in_type" => "cron"
"kind" => "start"
]
)

Expand All @@ -85,11 +90,10 @@ def stop_scheduler
it "logs an error if it fails" do
cron_checkin.start

stub_check_in_request(
stubs << stub_cron_check_in_request(
:events => [
"identifier" => "cron-checkin-name",
"kind" => "start",
"check_in_type" => "cron"
"kind" => "start"
],
:response => { :status => 499 }
)
Expand All @@ -111,11 +115,10 @@ def stop_scheduler
it "sends a cron check-in finish" do
cron_checkin.finish

stub_check_in_request(
stubs << stub_cron_check_in_request(
:events => [
"identifier" => "cron-checkin-name",
"kind" => "finish",
"check_in_type" => "cron"
"kind" => "finish"
]
)

Expand All @@ -134,11 +137,10 @@ def stop_scheduler
it "logs an error if it fails" do
cron_checkin.finish

stub_check_in_request(
stubs << stub_cron_check_in_request(
:events => [
"identifier" => "cron-checkin-name",
"kind" => "finish",
"check_in_type" => "cron"
"kind" => "finish"
],
:response => { :status => 499 }
)
Expand All @@ -159,31 +161,25 @@ def stop_scheduler
describe ".cron" do
describe "when a block is given" do
it "sends a cron check-in start and finish and return the block output" do
stub_check_in_request(
:events => [
stubs << stub_cron_check_in_request(
:events => [{
"identifier" => "cron-checkin-with-block",
"kind" => "start",
"check_in_type" => "cron"
]
)
stub_check_in_request(
:events => [
"kind" => "start"
}, {
"identifier" => "cron-checkin-with-block",
"kind" => "finish",
"check_in_type" => "cron"
]
"kind" => "finish"
}]
)

output = Appsignal::CheckIn.cron("cron-checkin-with-block") { "output" }
expect(output).to eq("output")
end

it "does not send a cron check-in finish event when an error is raised" do
stub_check_in_request(
stubs << stub_cron_check_in_request(
:events => [
"identifier" => "cron-checkin-with-block",
"kind" => "start",
"check_in_type" => "cron"
"kind" => "start"
]
)

Expand All @@ -195,11 +191,10 @@ def stop_scheduler

describe "when no block is given" do
it "only sends a cron check-in finish event" do
stub_check_in_request(
stubs << stub_cron_check_in_request(
:events => [
"identifier" => "cron-checkin-without-block",
"kind" => "finish",
"check_in_type" => "cron"
"kind" => "finish"
]
)

Expand Down
Loading

0 comments on commit 1971bbe

Please sign in to comment.