# Learning TDD With RSpec Part 3

This is part 3 (the last) in this series.

### Introduction:
In [part 2](https://blog.edssemuwemba.com/learning-tdd-with-rspec-part-2), this was achieved;
1. Scanning of each of the items, A, B, C, and D was done
2. The corresponding test was written for each

In part 3, this is going to be handled;
1. Some house cleaning 
2. Changing `CheckOut` Spec
3. Adding `PricingRules` Spec
4. Adding `PricingRules` Class
5. Adding pricing rules

### Some House Cleaning:
The code in the `ChecKOut` class needs to be included in a separate file under `lib/checkout.rb`

Below is the `checkout.rb` file:
```ruby
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
```

The `checkout` spec needs to included in a separate file under `spec/checkout_spec.rb`

Below is the `checkout_spec.rb` file:

```ruby
require 'checkout'

 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
   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
   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
 end

```


### Changing `CheckOut` Spec:

This is the new `checkout_spec.rb` file:

```ruby
require 'checkout'
require 'pricing_rules'

RSpec.describe 'checkout' do
  context 'when nothing has been scanned' do
    it 'shows 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 = PricingRules.new(
        "A" => {unit_price: 50, discount_price: 130, discount_count: 3})
      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 = PricingRules.new(
        "B" => {unit_price: 30, discount_price: 45, discount_count: 2})
      checkout = CheckOut.new(pricing_rules)
      checkout.scan('B')
      total = checkout.total

      expect(total).to eq 30
    end
  end

  context 'when C has been scanned' do·
    it 'shows the price of C' do
      pricing_rules = PricingRules.new("C" => {unit_price: 20})
      checkout = CheckOut.new(pricing_rules)
      checkout.scan('C')
      total = checkout.total

      expect(total).to eq 20
    end
  end

  context 'when D has been scanned' do·
    it 'shows the price of D' do
      pricing_rules = PricingRules.new("D" => {unit_price: 15})
      checkout = CheckOut.new(pricing_rules)
      checkout.scan('D')
      total = checkout.total

      expect(total).to eq 15
    end
  end
 end
```

This is the new `checkout.rb` file:

```ruby
 class CheckOut
   def initialize(pricing_rules)
     @pricing_rules = pricing_rules
     @items_count = Hash.new(0)·
   end
 
   def scan(item)
     @items_count[item] += 1
   end

   def total
     @items_count.inject(0) do |total, (item, count)|
       if item == 'B' || item == 'A'
         total += @pricing_rules.price_for(item, count)
       else
         total += count*@pricing_rules.unit_price(item)
       end
     end
   end
 end
```

### Adding `PricingRules` Spec:

This is the `pricing_rules_spec.rb` file;

```ruby
 require "pricing_rules"

 RSpec.describe PricingRules do
   describe "#unit_price" do
     it "fetches the unit price for the item" do
       pricing_rules = PricingRules.new("A" => {unit_price: 50})

       unit_price = pricing_rules.unit_price("A")

       expect(unit_price).to eq 50
     end
   end

   describe "#discount_price" do
     it "fetches the discount price for the item" do
       pricing_rules = PricingRules.new("A" => {discount_price: 130})

       discount_price = pricing_rules.discount_price("A")

       expect(discount_price).to eq 130
     end
   end

   describe "#discount_count" do
     it "fetches the discount count of the item" do
       pricing_rules = PricingRules.new("A" => {discount_count: 3})

       discount_count = pricing_rules.discount_count("A")
 
       expect(discount_count).to eq 3
     end
   end

   describe "#price_for" do
     it "calculates the price for a number of items" do

       pricing_rules = PricingRules.new(
         "A" => {unit_price: 50, discount_price: 130, discount_count: 3})

       price = pricing_rules.price_for("A", 3)

      expect(price).to eq 130
    end
  end

  describe "#has_dicount_price" do
    it "checks if an item has a discount price" do
      pricing_rules = PricingRules.new("A" => {discount_price: 130})

      has_discount = pricing_rules.has_discount_price("A")

      expect(has_discount).to be(true)
    end
  end
end
```

### Adding `PricingRules` Class:

This is the `pricing_rules.rb` file:

```ruby
 class PricingRules·
   def initialize(rules)
     @rules = rules
   end

   def unit_price(item)
     @rules[item][:unit_price]
   end

   def discount_price(item)
     @rules[item][:discount_price]
   end

   def discount_count(item)
     @rules[item][:discount_count]
   end

   def price_for(item, count)
     count/discount_count(item) * discount_price(item) +·
     count% discount_count(item) * unit_price(item)·
   end

   def has_discount_price(item)
     @rules[item].has_key?(:discount_price)
   end
 end
```

When the `pricing_rules_spec` tests are run, they pass successfully;

```ruby
./bin/rspec spec/pricing_rules_spec.rb:3
Run options: include {:locations=>{"./spec/pricing_rules_spec.rb"=>[3]}}

Randomized with seed 46467

PricingRules
  #has_dicount_price
    checks if an item has a discount price
  #price_for
    calculates the price for a number of items
  #discount_price
    fetches the discount price for the item
  #discount_count
    fetches the discount count of the item
  #unit_price
    fetches the unit price for the item

Finished in 0.0013 seconds (files took 0.05617 seconds to load)
5 examples, 0 failures

Randomized with seed 46467


Press ENTER or type command to continue

```

### Adding Pricing Rules:

Edit the `checkout.rb` file to include `pricing_rules` in the `initialize` method.

The `checkout.rb` file changes to:

```ruby
class CheckOut
  def initialize(pricing_rules)
    @pricing_rules = 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
```

Make certain that written tests still pass and they do as shown below:

```ruby
./bin/rspec spec/check_out_spec.rb

Randomized with seed 13654
.....

Finished in 0.00245 seconds (files took 0.05355 seconds to load)
5 examples, 0 failures

Randomized with seed 13654


Press ENTER or type command to continue
```

Edit the `chekout.rb` file further to this;

```ruby
class CheckOut
  def initialize(pricing_rules)
    @pricing_rules = pricing_rules
    @items_count = Hash.new(0)·
  end
··
  def scan(item)
    @items_count[item] += 1
  end

  def total
    @items_count.inject(0) do |total, (item, count)|
      if item == 'B' || item == 'A'
        total += @pricing_rules.price_for(item, count)
      else
        total += count*@pricing_rules.unit_price(item)
      end
    end
  end
end
```

This was done to take into account the `pricing_rules` and `items_count`

Edit the `checkout_spec.rb` file to gradually include pricing rules information

#### Adding Pricing Rules To A
Include `pricing_rules` for the context `'when A has been scanned'`:

```ruby
context 'when A has been scanned' do
  it 'shows the price of A' do
    pricing_rules = PricingRules.new(
      "A" => {unit_price: 50, discount_price: 130, discount_count: 3})
    checkout = CheckOut.new(pricing_rules)

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

    expect(total).to eq 50
  end
end
```

When the test is run, it passes successfully:

```ruby
./bin/rspec spec/checkout_spec.rb:16
Run options: include {:locations=>{"./spec/checkout_spec.rb"=>[16]}}

Randomized with seed 64567

checkout
  when A has been scanned
    shows the price of A

Finished in 0.00064 seconds (files took 0.04095 seconds to load)
1 example, 0 failures

Randomized with seed 64567


Press ENTER or type command to continue

```

#### Adding Pricing Rules to the rest of the items:

```ruby
   context 'when B has been scanned' do
     it 'shows the price of B' do
       pricing_rules = PricingRules.new(
         "B" => {unit_price: 30, discount_price: 45, discount_count: 2})
       checkout = CheckOut.new(pricing_rules)
       checkout.scan('B')
       total = checkout.total

       expect(total).to eq 30
     end
   end

   context 'when C has been scanned' do·
     it 'shows the price of C' do
       pricing_rules = PricingRules.new("C" => {unit_price: 20})
       checkout = CheckOut.new(pricing_rules)
       checkout.scan('C')
       total = checkout.total

       expect(total).to eq 20
     end
   end

   context 'when B, A and then D have been scanned' do
     it 'shows the total price of B, A and D' do
       pricing_rules = PricingRules.new(
         "B" => {unit_price: 30, discount_price: 45, discount_count: 2},
         "A" => {unit_price: 50, discount_price: 130, discount_count: 3},
         "D" => {unit_price: 15}
       )
       checkout = CheckOut.new(pricing_rules)

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

       expect(total).to eq 95
     end
   end

  context 'when D has been scanned' do·
    it 'shows the price of D' do
      pricing_rules = PricingRules.new("D" => {unit_price: 15})
      checkout = CheckOut.new(pricing_rules)
      checkout.scan('D')
      total = checkout.total

      expect(total).to eq 15
    end
  end

  context 'when B, A and another B has been scanned' do
    it 'shows the total discounted price of B plus price of A' do
      pricing_rules = PricingRules.new(
        "B" => {unit_price: 30, discount_price: 45, discount_count: 2},
        "A" => {unit_price: 50, discount_price: 130, discount_count: 3},
        "D" => {unit_price: 15}
      )
      checkout = CheckOut.new(pricing_rules)

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

      expect(total).to eq 95
    end
  end

  context 'when A, A, and another A have been scanned' do
    it 'shows the total discounted price of three A items' do
      pricing_rules = PricingRules.new(
        "A" => {unit_price: 50, discount_price: 130, discount_count: 3})
      checkout = CheckOut.new(pricing_rules)

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

      expect(total).to eq 130
     end
   end
```
Notice that some new contexts have been added;
1.  'when B, A and then D have been scanned'
2.  'when B, A and then D have been scanned'
3.  'when A, A, and another A have been scanned'

These new contexts are meant to handle the situations for discounts.

These tests run successfully;

```ruby
./bin/rspec spec/checkout_spec.rb:4
Run options: include {:locations=>{"./spec/checkout_spec.rb"=>[4]}}

Randomized with seed 1369

checkout
  when B, A and another B has been scanned
    shows the total discounted price of B plus price of A
  when nothing has been scanned
    shows total of zero
  when A has been scanned
    shows the price of A
  when D has been scanned
    shows the price of D
  when B has been scanned
    shows the price of B
  when A, A, and another A have been scanned
    shows the total discounted price of three A items
  when C has been scanned
    shows the price of C
  when B, A and then D have been scanned
    shows the total price of B, A and D

Finished in 0.00125 seconds (files took 0.05214 seconds to load)
8 examples, 0 failures

Randomized with seed 1369


Press ENTER or type command to continue

```

### Conclusion:
This concludes the series.

Shout out to [Rob](https://twitter.com/purinkle) for his immense assistance on this and making it easy to comprehend.

The final project can be found [here](https://github.com/Esseme/checkout_kata).






