End to end to contracts

types of tests

Unit tests
  • test in isolation
  • fake dependencies
  • no consumers
  • fast
  • n boundary conditions
Integration tests
  • test in composition
  • real dependencies
  • no consumers
  • n 2 boundary conditions
End to end tests
  • test in situ
  • real dependencies
  • real consumers
  • n! boundary conditions

tdd with e2e

Acceptance criteria
  GIVEN I am on the index page
  WHEN  I click on a blog post
  THEN  I should see the post's title
e2e test

  create_blog_post(title: 'Foobar')
  visit '/posts'
  click_link 'view Foobar'
  expect(page).to_contain('Foobar')
        
failure steps
  1. blog post model doesn't exist
  2. blog post model doesn't have 'title'
  3. route '/posts' doesn't match anything
  4. link doesn't exist
  5. expectation failure (finally!!!)
e2e flow
  • write a metric shit-ton of setup
  • write an expectation
  • debug some broken thing...
  • debug more broken things...
  • debug more broken things...
  • one expectation passes
  • write more tests (probably unit tests) because zero confidence

introducing contracts

obligations and benefits

  def inverse(x)
    return 1 / x
  end
        
obligations
  • x > 0
  • x < maxint
benefits
  • result > 0
unit testing something which uses inverse

  def half_inverse(x)
    return inverse(x) / 2
  end
        
  • spy on inverse - stub to return something that satisfies it's benefits
  • call the method under test
  • assert all calls to inverse satisfied it's obligations
  • assert result matches what I expect given a particular stubbed value from inverse

tdd with unit tests / contracts

Acceptance criteria
  GIVEN I am on the index page
  WHEN  I click on a blog post
  THEN  I should see the post's title
unit test (view)

  contract_spy_blog_post(title: 'Foobar')
  render
  expect(page).to_contain('Foobar')
        
failure steps
  1. expectation failure
  2. unfulfilled contract 'blog_post.title -> string'
  3. unfulfilled contract 'render this view'
  4. unfulfilled contract 'assign a blog post'
unit test (model)

  post = create_blog_post(title: 'Foobar')
  expect(post.title).to_equal('Foobar')
        
failure steps
  1. blog_post doesn't have 'title'
  2. expectation failure
  3. still have unfulfilled contract 'render this view'
  4. still have unfulfilled contract 'assign a blog post'
unit test (controller)

  contract_spy_blog_post_repo(find: blog_post)
  get(:show)
  expect(response).to_render_view
  expect(response).to_assign(blog_post)
        
failure steps
  1. expectation failure
  2. unfulfilled contract 'repo.find -> blog_post'
  3. go back to the model test and fulfill that contract...
contract flow
  • pick one AC
  • write one unit test
  • get one expectation failure
  • make it pass
  • get an unfulfilled contract
  • write one unit test
  • make it pass
  • repeat until no failing tests, no unfulfilled contracts
  • go to next AC

resources