Skip to content

Commit

Permalink
Add tests for App module
Browse files Browse the repository at this point in the history
Testing the backwards logic was too hard, so I reversed it. i.e.
`include_test_difficulty_level?` is easier to reason about than.
`exclude_test_difficulty_level?`

I also realized, through testing, that the previous attempt at
memoization wasn't going to work since `a ||= false` will run every
time. So we have to do an explicit Nil check, and only run the logic
if the class variable is `#nil?`.

Next, I started seeing random testing errors, which is never good or
fun to track down. But I had a suspicion straight away that it was due
to how we're memoizing state into the App class during the App test
examples. So, i added a `before` block to pre-set that class variable
back to `nil` before each test example run.

However, that wasn't all! There was another place where class state
was being affected. Which was in the DifficultyLevel::SETTINGS_MAP
constant. Since we conditionally exclude the "Test" Difficulty Level,
based on the result of our method under test:
App.include_test_difficulty_level?. The easiest way to fix this is to
lazy-initialize the "Settings Map" in Difficulty Level. i.e. don't load
it as class load time, but rather, only after it is first accessed.
This allows the "running" class to determine whether or not  to include
the "Test" difficulty level vs the "loaded" class. Annndddd... this is
part of hwy lazy loading is one of the tenants of OOD. i.e. putting off
loading a thing until the last second.
- We didn't run into this before because before, our memoization of
  App.include_test_difficulty_level? wasn't working properly. After
  fixing it, we started seeing Class-level state issues in
  DifficultyLevel.
  • Loading branch information
pdobb committed Sep 6, 2024
1 parent 34f5fed commit d3e3517
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 14 deletions.
11 changes: 8 additions & 3 deletions app/models/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@

# App is utility module specific to this Rails Application and its environment.
module App
def self.exclude_test_difficulty_level?
@exclude_test_difficulty_level ||=
!debug? && Game.for_difficulty_level_test.none?
# :reek:NilCheck
def self.include_test_difficulty_level?
if @include_test_difficulty_level.nil?
@include_test_difficulty_level =
debug? || Game.for_difficulty_level_test.any?
else
@include_test_difficulty_level
end
end

def self.debug? = Rails.configuration.debug
Expand Down
24 changes: 14 additions & 10 deletions app/models/difficulty_level.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ class DifficultyLevel
# DifficultyLevel processing.
Error = Class.new(StandardError)

SETTINGS_MAP = {
TEST = "Test" => { columns: 3, rows: 3, mines: 1 },
"Beginner" => { columns: 9, rows: 9, mines: 10 },
"Intermediate" => { columns: 16, rows: 16, mines: 40 },
"Expert" => { columns: 30, rows: 16, mines: 99 },
}.tap { |hash|
hash.except!(TEST) if App.exclude_test_difficulty_level?
}.freeze
TEST = "Test"

def self.settings_map
@settings_map ||= {
TEST => { columns: 3, rows: 3, mines: 1 },
"Beginner" => { columns: 9, rows: 9, mines: 10 },
"Intermediate" => { columns: 16, rows: 16, mines: 40 },
"Expert" => { columns: 30, rows: 16, mines: 99 },
}.tap { |hash|
hash.except!(TEST) unless App.include_test_difficulty_level?
}.freeze
end

attr_reader :name

Expand All @@ -26,7 +30,7 @@ def self.all
end

def self.names
SETTINGS_MAP.keys
settings_map.keys
end

def self.build_random
Expand Down Expand Up @@ -67,7 +71,7 @@ def rows = settings.fetch(:rows)
def mines = settings.fetch(:mines)

def settings
SETTINGS_MAP.fetch(name)
self.class.settings_map.fetch(name)
end

private
Expand Down
113 changes: 113 additions & 0 deletions test/models/app_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# frozen_string_literal: true

require "test_helper"

class AppTest < ActiveSupport::TestCase
describe "App" do
let(:unit_class) { App }

subject { unit_class }

describe ".include_test_difficulty_level?" do
before do
App.instance_variable_set(:@include_test_difficulty_level, nil)
end

after do
App.instance_variable_set(:@include_test_difficulty_level, nil)
end

context "GIVEN App.debug? = true" do
before do
MuchStub.(unit_class, :debug?) { true }
end

context "WHETHER OR NOT any 'Test' Games exist" do
before do
MuchStub.tap(Game, :for_difficulty_level_test) { |arel|
MuchStub.(arel, :any?) { [true, false].sample }
}
end

it "returns true" do
_(subject.include_test_difficulty_level?).must_equal(true)
end
end
end

context "GIVEN App.debug? = false" do
before do
MuchStub.(unit_class, :debug?) { false }
end

context "GIVEN 'Test' Games do exist" do
before do
MuchStub.tap(Game, :for_difficulty_level_test) { |arel|
MuchStub.(arel, :any?) { true }
}
end

it "returns true" do
_(subject.include_test_difficulty_level?).must_equal(true)
end
end

context "GIVEN no 'Test' Games exist" do
before do
MuchStub.tap(Game, :for_difficulty_level_test) { |arel|
MuchStub.(arel, :any?) { false }
}
end

it "returns false" do
_(subject.include_test_difficulty_level?).must_equal(false)
end
end

context "GIVEN multiple calls" do
before do
@any_called_count = 0
MuchStub.tap(Game, :for_difficulty_level_test) { |arel|
MuchStub.(arel, :any?) {
@any_called_count += 1
false
}
}
end

it "uses a memoized result" do
subject.include_test_difficulty_level?
subject.include_test_difficulty_level?
_(@any_called_count).must_equal(1)
end
end
end
end

describe ".debug?" do
context "GIVEN Rails.configuration.debug = true" do
before do
MuchStub.tap(Rails, :configuration) { |config|
MuchStub.(config, :debug) { true }
}
end

it "returns true" do
_(subject.debug?).must_equal(true)
end
end

context "GIVEN Rails.configuration.debug = false" do
before do
MuchStub.tap(Rails, :configuration) { |config|
MuchStub.(config, :debug) { false }
}
end

it "returns false" do
_(subject.debug?).must_equal(false)
end
end
end
end
end
3 changes: 2 additions & 1 deletion test/models/random_difficulty_level_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ class RandomDifficultyLevelTest < ActiveSupport::TestCase
subject { unit_class }

describe "#initialize" do
it "returns the expected instance" do
it "returns a frozen RandomDifficultyLevel instance" do
result = subject.new
_(result).must_be_instance_of(unit_class)
_(result.frozen?).must_equal(true)
end
end
end
Expand Down

0 comments on commit d3e3517

Please sign in to comment.