Ed SSemuwemba
Ed SSemuwemba's Blog

Ed SSemuwemba's Blog

Learning TDD With RSpec Part 2

Photo by Simon Kadula on Unsplash

Learning TDD With RSpec Part 2

Checkout Kata Part 2

Ed SSemuwemba's photo
Ed SSemuwemba
·Jul 29, 2022·

8 min read

This is part 2 of a planned 3 part series

Introduction:

In part 1, this was achieved;

  1. Initialising project
  2. Writing a test for when nothing has been scanned

In part 2, this is going to be handled;

  1. Scanning of A
  2. Scanning of B
  3. Scanning of C
  4. Scanning of D

Tests will be run using;

  1. ⎵ + t : run entire test ( means the keyboard space bar button)
  2. ⎵ + l : run last test
  3. ⎵ + s: run nearest test

Scanning of A:

Add another context to check for when A has been scanned. The file changes to this;

class CheckOut
  def initialize(pricing_rules)

  end

  def total
    0
  end
end

RSpec.describe 'checkout' do
  context 'when nothing has been scanned' do
    it 'shows a total of zero' do
      pricing_rules = :Rule
      checkout = CheckOut.new(pricing_rules)
      total = checkout.total

      expect(total).to eq 0
    end
  end
  context 'when A has been scanned' do
    it 'shows the price of A' do

      expect(total).to eq 50
    end
  end
end

Run the test by pressing ⎵ + t. We get this error;

~
./bin/rspec spec/check_out_spec.rb

Randomized with seed 34364
.F

Failures:

  1) checkout when A has been scanned shows the price of A
     Failure/Error: expect(total).to eq 50

     NameError:
       undefined local variable or method `total' for #<RSpec::ExampleGroups::Checkout::WhenAHasBeenScanned "shows the price of A" (./spec/check_out_spec.rb:22)>
     # ./spec/check_out_spec.rb:24:in `block (3 levels) in <top (required)>'

Finished in 0.00256 seconds (files took 0.05539 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./spec/check_out_spec.rb:22 # checkout when A has been scanned shows the price of A

Randomized with seed 34364


shell returned 1

Press ENTER or type command to continue

Modify the file to add the lines specifying what total is and define pricing_rules;

class CheckOut
  def initialize(pricing_rules)

  end

  def total
    0
  end
end

RSpec.describe 'checkout' do
  context 'when nothing has been scanned' do
    it 'shows a total of zero' do
      pricing_rules = :Rule
      checkout = CheckOut.new(pricing_rules)
      total = checkout.total

      expect(total).to eq 0
    end
  end
  context 'when A has been scanned' do
    it 'shows the price of A' do
      pricing_rules = :Rule
      checkout = CheckOut.new(pricing_rules)
      total = checkout.total

      expect(total).to eq 50
    end
  end
end

When the test is run, as expected we get the error as shown below;

./bin/rspec spec/check_out_spec.rb

Randomized with seed 5579
.F

Failures:

  1) checkout when A has been scanned shows the price of A
     Failure/Error: expect(total).to eq 50

       expected: 50
            got: 0

       (compared using ==)
     # ./spec/check_out_spec.rb:27:in `block (3 levels) in <top (required)>'

Finished in 0.00758 seconds (files took 0.0523 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./spec/check_out_spec.rb:22 # checkout when A has been scanned shows the price of A

Randomized with seed 5579


shell returned 1

Press ENTER or type command to continue

A change needs to be made to the spec to include scanning of Item A. This changes the file to be;

 class CheckOut
   def initialize(pricing_rules)

   end

   def total
     0
   end
 end

 RSpec.describe 'checkout' do
   context 'when nothing has been scanned' do
     it 'shows a total of zero' do
       pricing_rules = :Rule
       checkout = CheckOut.new(pricing_rules)
       total = checkout.total

       expect(total).to eq 0
     end
   end
   context 'when A has been scanned' do
     it 'shows the price of A' do
       pricing_rules = :Rule
       checkout = CheckOut.new(pricing_rules)

       checkout.scan('A')
       total = checkout.total

       expect(total).to eq 50
     end
   end
 end

When checkout.scan('A') is added, we get this error;

./bin/rspec spec/check_out_spec.rb

Randomized with seed 45438
.F

Failures:

  1) checkout when A has been scanned shows the price of A
     Failure/Error: checkout.scan('A')

     NoMethodError:
       undefined method `scan' for #<CheckOut:0x0000000130ca8448>
     # ./spec/check_out_spec.rb:26:in `block (3 levels) in <top (required)>'

Finished in 0.00187 seconds (files took 0.04966 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./spec/check_out_spec.rb:22 # checkout when A has been scanned shows the price of A

Randomized with seed 45438


shell returned 1

Press ENTER or type command to continue

Add the method: scan(item) to CheckOut class to fix this error;

class CheckOut
  def initialize(pricing_rules)

  end

  def total
    0
  end

  def scan(item)

  end
end

Now we are back to the previous error when the test is run;

./bin/rspec spec/check_out_spec.rb

Randomized with seed 56370
.F

Failures:

  1) checkout when A has been scanned shows the price of A
     Failure/Error: expect(total).to eq 50

       expected: 50
            got: 0

       (compared using ==)
     # ./spec/check_out_spec.rb:33:in `block (3 levels) in <top (required)>'

Finished in 0.00768 seconds (files took 0.04974 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./spec/check_out_spec.rb:26 # checkout when A has been scanned shows the price of A

Randomized with seed 56370


shell returned 1

Press ENTER or type command to continue

To fix this error, we make three changes to the CheckOut class as below;


 class CheckOut
   def initialize(pricing_rules)
     @total = 0
   end

   def total
     @total
   end

   def scan(item)
     @total = 50
   end
 end

Now when the test is run, all the current two tests will pass successfully;

./bin/rspec spec/check_out_spec.rb

Randomized with seed 4857
..

Finished in 0.00201 seconds (files took 0.04948 seconds to load)
2 examples, 0 failures

Randomized with seed 4857


Press ENTER or type command to continue

Scanning of B:

Add another context to check for when B has been scanned. Add the context to the RSpec.describe 'checkout' block.

The file changes to this;

 RSpec.describe 'checkout' do
   context 'when nothing has been scanned' do
     it 'shows a total of zero' do
       pricing_rules = :Rule
       checkout = CheckOut.new(pricing_rules)
       total = checkout.total

       expect(total).to eq 0
     end
   end
   context 'when A has been scanned' do
     it 'shows the price of A' do
       pricing_rules = :Rule
       checkout = CheckOut.new(pricing_rules)

       checkout.scan('A')
       total = checkout.total

       expect(total).to eq 50
     end
   end
   context 'when B has been scanned' do
     it 'shows the price of B' do
       pricing_rules = :Rule
       checkout = CheckOut.new(pricing_rules)

       checkout.scan('B')
       total = checkout.total

       expect(total).to eq 30
     end
   end
 end

When ⎵ + s is pressed on the keyboard to only run this new test, the following error is displayed;

./bin/rspec spec/check_out_spec.rb:36
Run options: include {:locations=>{"./spec/check_out_spec.rb"=>[36]}}

Randomized with seed 53332
F

Failures:

  1) checkout when B has been scanned shows the price of B
     Failure/Error: expect(total).to eq 30

       expected: 30
            got: 50

       (compared using ==)
     # ./spec/check_out_spec.rb:44:in `block (3 levels) in <top (required)>'

Finished in 0.00643 seconds (files took 0.0565 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/check_out_spec.rb:37 # checkout when B has been scanned shows the price of A

Randomized with seed 53332


shell returned 1

Press ENTER or type command to continue

To fix this, make changes to the scan method in the CheckOut class;

class CheckOut
  def initialize(pricing_rules)
    @total = 0
  end

  def total
    @total
  end

  def scan(item)
    if item == 'A'
      @total = 50
    elsif item == 'B'
      @total = 30
    end
  end
end

Now when press ⎵ + l to run the last test, the test for scanning B now passes as well;

./bin/rspec spec/check_out_spec.rb:40
Run options: include {:locations=>{"./spec/check_out_spec.rb"=>[40]}}

Randomized with seed 59538
.

Finished in 0.00059 seconds (files took 0.05165 seconds to load)
1 example, 0 failures

Randomized with seed 59538


Press ENTER or type command to continue

Scanning of C:

Add another context to check for when C has been scanned. Add the context to the RSpec.describe 'checkout' block.

This is the context;

   context 'when C has been scanned' do
     it 'shows the price of C' do
       pricing_rules = :Rule
       checkout = CheckOut.new(pricing_rules)

       checkout.scan('C')
       total = checkout.total

       expect(total).to eq 20
     end
   end

When + s is pressed to run this new test, this error results;

./bin/rspec spec/check_out_spec.rb:59
Run options: include {:locations=>{"./spec/check_out_spec.rb"=>[59]}}

Randomized with seed 51710
F

Failures:

  1) checkout when C has been scanned shows the price of C
     Failure/Error: expect(total).to eq 20

       expected: 20
            got: 0

       (compared using ==)
     # ./spec/check_out_spec.rb:59:in `block (3 levels) in <top (required)>'

Finished in 0.00602 seconds (files took 0.05125 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/check_out_spec.rb:52 # checkout when C has been scanned shows the price of C

Randomized with seed 51710


shell returned 1

Press ENTER or type command to continue

To fix this, make changes to the CheckOut class.

Do a check for item C like so;

 class CheckOut
   def initialize(pricing_rules)
     @total = 0
   end

   def total
     @total
   end

   def scan(item)
     if item == 'A'
       @total = 50
     elsif item == 'B'
       @total = 30
     elsif item == 'C'
       @total = 20
     end
   end
 end

And now when the test is run, it should be pass;

./bin/rspec spec/check_out_spec.rb:59
Run options: include {:locations=>{"./spec/check_out_spec.rb"=>[59]}}

Randomized with seed 2878
.

Finished in 0.00068 seconds (files took 0.05424 seconds to load)
1 example, 0 failures

Randomized with seed 2878


Press ENTER or type command to continue

Scanning of D:

Add another context to check for when D has been scanned. Add the context to the RSpec.describe 'checkout' block.

This is the context;

   context 'when D has been scanned' do
     it 'shows the price of D' do
       pricing_rules = :Rule
       checkout = CheckOut.new(pricing_rules)

       checkout.scan('D')
       total = checkout.total

       expect(total).to eq 15
     end
   end

When + s is pressed to run this new test, this error results;

./bin/rspec spec/check_out_spec.rb:72
Run options: include {:locations=>{"./spec/check_out_spec.rb"=>[72]}}

Randomized with seed 61671
F

Failures:

  1) checkout when D has been scanned shows the price of D
     Failure/Error: expect(total).to eq 15

       expected: 15
            got: 0

       (compared using ==)
     # ./spec/check_out_spec.rb:72:in `block (3 levels) in <top (required)>'

Finished in 0.00622 seconds (files took 0.0561 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/check_out_spec.rb:65 # checkout when D has been scanned shows the price of D

Randomized with seed 61671


shell returned 1

Press ENTER or type command to continue

To fix this, make changes to the CheckOut class.

Add an else to check for item D;

 class CheckOut
   def initialize(pricing_rules)
     @total = 0
   end

   def total
     @total
   end

   def scan(item)
     if item == 'A'
       @total = 50
     elsif item == 'B'
       @total = 30
     elsif item == 'C'
       @total = 20
     else 
       @total = 15
     end
   end
 end

And now when the test is run, it should be pass;

./bin/rspec spec/check_out_spec.rb:72
Run options: include {:locations=>{"./spec/check_out_spec.rb"=>[72]}}

Randomized with seed 38120
.

Finished in 0.00059 seconds (files took 0.05152 seconds to load)
1 example, 0 failures

Randomized with seed 38120


Press ENTER or type command to continue

Conclusion:

This concludes part 2.

In part 3, this will handled;

  1. refactoring to make the code better.
  2. taking the pricing rules into account

Shout out to Rob for his immense assistance on this and making it easy to comprehend.

 
Share this