Hi,
According to this article, I try to not use let in a test suite. I’m trying to test this class :
class PaintingOrderCreator < OrderCreator
attr_accessor :painting, :order, :payment, :form, :error_messages,
:painting_price
def initialize(painting:, order:, painting_price:, payment:, form:)
super(payment: payment, form: form, order: order)
@painting = painting
@painting_price = painting_price
end
def process
if super
change_painting_prices
assign_period_to_painting
painting.published? ? extend_painting : send_mail_to_administrators
end
payment.process if order.amount != 0
end
def fill_attributes(attributes)
form.from_hash(attributes)
order.painting = painting
order.user = painting.owner.user
order.description = painting.title
order.period_id = attributes['period_id']
order.amount = PriceCalculator.new(painting_price: painting_price,
period: order.period).price
apply_coupon(attributes['coupon_code'])
end
private
def apply_coupon(code)
CouponCalculator.new(order: order, code: code).apply if code
end
def assign_period_to_painting
painting.update_attribute('period', order.period)
end
def extend_painting
PaintingPublisher.new(painting: painting).publish
end
def send_mail_to_administrators
OrderMailer.send_mail_after_creation_to_administrators(order).deliver
end
def change_painting_prices
changing_date = Date.today + order.period.duration.day - 1.day
painting.current_painting_price.update_attribute('end_date', changing_date)
painting_price.update_attribute('begin_date', changing_date)
painting_price.update_attribute('end_date', nil)
end
end
This class is responsible to create an order. It must to send an email in some case, change the periods for prices, publish the painting, etc…
This is my spec :
require 'spec_helper'
describe PaintingOrderCreator do
describe "#process" do
context "when the form are valid" do
context "when form is saved" do
it "update the period of the painting" do
order = build_stubbed(:order)
painting = build_stubbed(:painting)
payment = double(process: nil)
period = double(duration: 1)
painting_price = double(update_attribute: nil)
form = double(save: true)
painting_publisher = double(publish: nil)
allow(PaintingPublisher).to receive(:new).
and_return(painting_publisher)
allow(order).to receive(:period).and_return(period)
allow(painting).to receive(:current_painting_price).
and_return(painting_price)
subject = PaintingOrderCreator.new(painting: painting,
order: order,
painting_price: painting_price,
form: form,
payment: payment)
allow(subject).to receive(:valid?).and_return(true)
expect(painting).to receive(:update_attribute).with('period', period)
subject.process
end
context "when the painting is already published" do
it "extend the publishing date" do
order = build_stubbed(:order)
painting = build_stubbed(:painting)
payment = double(process: nil)
painting_publisher = double(publish: nil)
form = double(save: true)
painting_price = double(update_attribute: nil)
allow(painting).to receive(:current_painting_price).
and_return(painting_price)
allow(PaintingPublisher).to receive(:new).
and_return(painting_publisher)
allow(painting).to receive(:update_attribute)
painting.published = true
subject = PaintingOrderCreator.new(painting: painting,
order: order,
painting_price: painting_price,
form: form,
payment: payment)
allow(subject).to receive(:valid?).and_return(true)
subject.process
expect(painting_publisher).to have_received(:publish)
end
end
context "when the painting is not published" do
it "send an email to administrators" do
order = build_stubbed(:order)
painting = build_stubbed(:painting, published: false)
painting_price = double(update_attribute: nil)
payment = double(process: nil)
form = double(save: true)
mailer = double(deliver: nil)
allow(painting).to receive(:update_attribute)
allow(painting).to receive(:current_painting_price).
and_return(painting_price)
subject = PaintingOrderCreator.new(painting: painting,
order: order,
painting_price: painting_price,
form: form,
payment: payment)
allow(subject).to receive(:valid?).and_return(true)
allow(OrderMailer).to \
receive(:send_mail_after_creation_to_administrators).
and_return(mailer)
subject.process
end
end
context "when the painting have more than one painting price" do
it "set the end date of the current painting price" do
order = build_stubbed(:order)
painting = build_stubbed(:painting, published: true)
painting_publisher = double(publish: nil)
form = double(save: true)
payment = double(process: nil)
allow(PaintingPublisher).to receive(:new).
and_return(painting_publisher)
allow(painting).to receive(:update_attribute)
painting_price = build_stubbed(:painting_price)
period = order.period
subject = PaintingOrderCreator.new(painting: painting,
order: order,
painting_price: painting_price,
form: form,
payment: payment)
allow(painting_price).to receive(:update_attribute)
make_different_current_painting_price_for(painting)
new_end_date = Date.today + period.duration.day - 1
subject.process
expect(painting.current_painting_price).to \
have_received(:update_attribute).with('end_date', new_end_date)
end
it "set the begin date of the next painting price" do
order = build_stubbed(:order)
painting = build_stubbed(:painting, published: true)
painting_publisher = double(publish: nil)
form = double(save: true)
payment = double(process: nil)
allow(PaintingPublisher).to receive(:new).
and_return(painting_publisher)
allow(painting).to receive(:update_attribute)
painting_price = build_stubbed(:painting_price)
period = order.period
subject = PaintingOrderCreator.new(painting: painting,
order: order,
painting_price: painting_price,
form: form,
payment: payment)
allow(painting_price).to receive(:update_attribute)
make_different_current_painting_price_for(painting)
new_begin_date = Date.today + period.duration.day - 1.day
expect(painting_price).to \
receive(:update_attribute).with('begin_date', new_begin_date)
subject.process
end
it "set the end date of the next painting price to nil" do
order = build_stubbed(:order)
painting = build_stubbed(:painting, published: true)
painting_publisher = double(publish: nil)
form = double(save: true)
payment = double(process: nil)
allow(PaintingPublisher).to receive(:new).
and_return(painting_publisher)
allow(painting).to receive(:update_attribute)
painting_price = build_stubbed(:painting_price)
subject = PaintingOrderCreator.new(painting: painting,
order: order,
painting_price: painting_price,
form: form,
payment: payment)
allow(painting_price).to receive(:update_attribute)
make_different_current_painting_price_for(painting)
expect(painting_price).to \
receive(:update_attribute).with('end_date', nil)
subject.process
end
end
def make_different_current_painting_price_for(painting)
current_painting_price = painting.current_painting_price
allow(painting).to receive(:painting_prices).and_return([double, double])
allow(painting).to receive(:current_painting_price).
and_return(current_painting_price)
allow(current_painting_price).to receive(:update_attribute)
end
end
end
end
describe "#fill_attributes" do
it "fill form from hash" do
order = build_stubbed(:order)
painting = build_stubbed(:painting)
form = double
attributes = {}
price_calculator = double(price: nil)
allow(PriceCalculator).to receive(:new).and_return(price_calculator)
subject = PaintingOrderCreator.new(painting: painting,
order: order,
painting_price: double,
form: form,
payment: double)
expect(form).to receive(:from_hash).with(attributes)
subject.fill_attributes(attributes)
end
it "set the order painting" do
painting = build_stubbed(:painting)
order = build_stubbed(:order, painting: nil)
form = double(from_hash: nil)
price_calculator = double(price: nil)
allow(PriceCalculator).to receive(:new).and_return(price_calculator)
subject = PaintingOrderCreator.new(painting: painting,
order: order,
painting_price: double,
form: form,
payment: double)
subject.fill_attributes({})
expect(order.painting).to eq painting
end
it "set the order user" do
painting = build_stubbed(:painting)
order = build_stubbed(:order, user: nil)
form = double(from_hash: nil)
price_calculator = double(price: nil)
allow(PriceCalculator).to receive(:new).and_return(price_calculator)
subject = PaintingOrderCreator.new(painting: painting,
order: order,
painting_price: double,
form: form,
payment: double)
subject.fill_attributes({})
expect(order.user).to eq painting.owner.user
end
it "set the order description" do
painting = build_stubbed(:painting)
order = build_stubbed(:order, description: nil)
form = double(from_hash: nil)
price_calculator = double(price: nil)
allow(PriceCalculator).to receive(:new).and_return(price_calculator)
subject = PaintingOrderCreator.new(painting: painting,
order: order,
painting_price: double,
form: form,
payment: double)
subject.fill_attributes({})
expect(order.description).to eq painting.title
end
it "set the order's period" do
painting = build_stubbed(:painting)
order = build_stubbed(:order, period: nil)
form = double(from_hash: nil)
price_calculator = double(price: nil)
allow(PriceCalculator).to receive(:new).and_return(price_calculator)
subject = PaintingOrderCreator.new(painting: painting,
order: order,
painting_price: double,
form: form,
payment: double)
subject.fill_attributes({'period_id' => '1'})
expect(order.period_id).to eq 1
end
it "calculate order's amount" do
painting = build_stubbed(:painting)
order = build_stubbed(:order, amount: nil)
form = double(from_hash: nil)
subject = PaintingOrderCreator.new(painting: painting,
order: order,
painting_price: double,
form: form,
payment: double)
price_calculator = double(price: 100)
allow(PriceCalculator).to receive(:new).and_return(price_calculator)
subject.fill_attributes({})
expect(order.amount).to eq 100
end
context "when there is a coupon" do
it "apply the coupon" do
painting = build_stubbed(:painting)
order = build_stubbed(:order)
form = double(from_hash: nil)
price_calculator = double(price: nil)
allow(PriceCalculator).to receive(:new).and_return(price_calculator)
subject = PaintingOrderCreator.new(painting: painting,
order: order,
painting_price: double,
form: form,
payment: double)
code = 'TEST'
coupon_calculator = double
expect(CouponCalculator).to receive(:new).
with(order: order, code: code).
and_return(coupon_calculator)
expect(coupon_calculator).to receive(:apply)
subject.fill_attributes({'coupon_code' => code})
end
end
end
end
Each spec take 20 lines, it’s not dry at all, unreadable and hard to maintain.
What can I do? Should I do more functions to create objects in my spec? Should I change the private methods for public in the class? Should I do subclasses of the first class?
Any tips are welcomed, particularly from @jferris as author of the article.
Thanks!