Spec by Spec
Learn to build modular web apps with TDD and a use case approach.

Does TDD freeze you up?

Do you feel that you know the basics, but when you try to apply the concepts on a real app… You don’t know how to start? Or you don’t know what to test?

Your tests are very slow, and you are trying to write decoupled tests, but you don’t know how?

Your tests are getting very hard to write, and you want to use TDD to help you design decoupled code… but you are not really sure how to start.

Well, first of all… You are not alone! Believe me!

And I think that I can help you!!

And how can I help you… Maybe you are asking…

Well… I had a problem… every app that I was working on, had a test suite of 5 minutes or more! (some of them more than 15 minutes)… Have you been there?

I started to notice that the proposed solutions for this problem, were normally of two types. Run your tests in parallel or start a process to avoid the starting time of the application.

And although I think that this solutions have their place (mostly on integration tests), I started to think that this was looking more like a syntom that as a comunity, we are building applications that are very coupled with our tools and frameworks. That makes us end with applications that are hard to change and with tests suites that we don’t trust, and don’t run =(…

With this problems in mind I started to try different ways of structuring my applications, looking to…

After some years, I ended with an special approach that has help me to really feel the benefits above… Is nothing new but is a combination of many ideas, on what abstractions to use and how to design your software to make it more easy to tests.

And I wrote a book to help you learn this aproach…

In the book we are going to build an app together using TDD, by focusing on the requirements, and not in your model (or what you think is the right design), to expose an API… (Hint: This is not the Rails way).

module Restaurant
  def self.get_menu(*args)
    #...
  end

  def self.create_empty_order(*args)
    #...
  end

  def self.add_item_to_order(*args)
    #...
  end

  #...
end

You are going to build an app with your business logic decoupled from your tools and framework

post "/orders" do
  order_id = Restaurant.create_empty_order(orders_store)
  redirect to(edit_order_path(order_id))
end

get "/orders/:id/edit" do
  erb :edit, locals: {
    items: Restaurant.get_menu(items_store),
    order: Restaurant.get_order(params[:id], orders_store),
    status_options: Restaurant.get_order_status_options
  }
end

#...

And you will lean how you can delay decisions to the moment, when you have more information to take them…

RSpec.describe "Get menu" do
  #...
  describe "returns each item" do
    it "with name, price and description" do
      store = store_with([
        item_with(
          name: "D1",
          price: 110,
          description: "D1 desc"
        )
      ])

      item = get_menu(store).first
      expect(item.name).to eq "D1"
      expect(item.price).to eq 110
      expect(item.description).to eq "D1 desc"
    end
    #...
  end
end

We are going to build an app using ruby and rspec and we will deliver the application as a web app using the Sinatra web framework, although this last part will be just a detail, and you will be able to port the knowledge to Rails or even other languages.

We are going to build it really spec by spec, you are going to see each falling tests and the reason of every design decision.

Like this one…

1) Add item to order creates an order item record
   Failure/Error: item = items_store.find(item_id)

   NoMethodError:
     undefined method `find' for #<ItemsStore:0x007fda41acce70>

Or this other…

1) Add item to order updates the order item record, when an item is added more than once
   Failure/Error:
     expect(order_items_store).
       to receive(:update).
       with(order_item.id, quantity: 2)

     #<OrderItemsStore:0x007f967f8c12d0
     @all=[#<OrderItemRecord:0x007f967f8c1640 @id=1234>]> does not implement: update

And you will find code not definitions, lots of code…

Shall we start?

Yes, I want the book