Minitest and Fixtures

A talk on automated testing with sample data on Rails 5

Jenny Nguyen

@knockycode

RoRo Sydney July 2017

Talk Structure

Types of automated tests

What is Minitest and what are Fixtures?

 

Write and run tests

 

Continuous Testing: guard-minitest (Rails 5)

Types of automated tests

Models

Controllers

Views

Unit tests

  verify that a unit of code within a component works

 

Integration tests

  verify that the communication between components works

What is Minitest?

Testing framework for Ruby and for Rails

Improved version of Minitest is bundled with Rails 5

It supports two styles of syntax: assert-style & spec-style

# test/models/food_test.rb
# assert_style syntax

require 'test_helper'

class FoodTest < ActiveSupport::TestCase
  test 'must have a name' do
    food = Food.new
    assert food.invalid?
    assert_not_nil food.errors[:name]
  end
end
# test/models/food_test.rb
# spec-style syntax

require 'test_helper'

describe Food do
  it 'must have a name' do
    food = Food.new
    food.must_be :invalid?
    expect(food.errors[:name]).wont_be_nil
  end
end

What are Fixtures?

Sample data for a model

Very static sample data

Listed in a .yml file

# test/fixtures/foods.yml

pie:
  name: pie 6 pack
  notes: don't get 4 pack, that's too little

milk:
  name: milk 2L
  notes: don't get 3L as it won't fit in the fridge

migoreng:
  name: migoreng
  notes: get a box of them
# test/fixtures/shelves.yml

one:
  food: pie
  owner: jenny
  expiry_date: 2018-12-18
  quantity: 5

two:
  food: migoreng
  owner: maggie
  expiry_date: 2017-10-22
  quantity: 22

Let's get started with testing

For a Rails 5 app that keeps track of perishable food

Models

  Food, Shelf, Owner

Associations

  Food can belong to many Owners. Owners can have many Foods

  Shelf 'belongs' to a Food and to a Owner

Presence checks

  Shelf.expiry_date

  Shelf.quantity

Scope for Shelf

  expiring_food: get food nearing the expiry_date 

Generate boilerplate code

For a Rails 5 app that keeps track of perishable food

$ rails _5.0.0_ new perishable-food-tracker
[snip]
      create  test/fixtures
      create  test/fixtures/.keep
      create  test/fixtures/files
      create  test/fixtures/files/.keep
      create  test/controllers
      create  test/controllers/.keep
      create  test/mailers
      create  test/mailers/.keep
      create  test/models
      create  test/models/.keep
      create  test/helpers
      create  test/helpers/.keep
      create  test/integration
      create  test/integration/.keep
      create  test/test_helper.rb

Terminal

Add

require 'minitest/autorun'

to test_helper.rb

Generate the models

For a Rails 5 app that keeps track of perishable food

$ rails g model Owner name
[snip]
    invoke    test_unit
    create      test/models/owner_test.rb
    create      test/fixtures/owners.yml
$ rails g scaffold Food name notes:text
[snip]
      invoke    test_unit
      create      test/models/food_test.rb
      create      test/fixtures/foods.yml
[snip]
      invoke    test_unit
      create      test/controllers/foods_controller_test.rb
[snip]
$ rails g model Shelf food:references owner:references expiry_date:date quantity:integer
[snip]
      invoke    test_unit
      create      test/models/shelf_test.rb
      create      test/fixtures/shelves.yml
$ rails db:migrate

Terminal

3 common phases of a test

Before verifying, setup sample data

Act the logic on the sample data

Assert that the sample data got changed as expected

Test presence checks

# test/fixtures/shelves.yml

valid:
  food: one
  owner: one
  expiry_date: 2017-07-10
  quantity: 1

[snip]

Before verifying, setup sample data

# test/models/shelf_test.rb

require 'test_helper'

class ShelfTest < ActiveSupport::TestCase
  def setup
    @shelf = shelves(:valid)
  end

  test 'with setup sample data' do
    assert @shelf.valid?, 'should be valid'
  end
end

Test presence checks

Act the logic on the sample data

# test/models/shelf_test.rb

[snip]

  test 'with setup sample data' do
    assert @shelf.valid?, 'should be valid'
  end

  test 'with no expiry date' do
    @shelf.expiry_date = nil
  end

  test 'with no set quantity' do
    @shelf.quantity = nil
  end
end

Test presence checks

# test/models/shelf_test.rb

[snip]

  test 'with no expiry date' do
    @shelf.expiry_date = nil
    assert @shelf.invalid?, 'should be invalid'
    assert_not_nil @shelf.errors[:expiry_date], 'should have validation error'
  end

  test 'with no set quantity' do
    @shelf.quantity = nil
    assert @shelf.invalid?, 'should be invalid'
    assert_not_nil @shelf.errors[:quantity], 'should have validation error'
  end
end

Verify that the sample data got changed as expected

Test presence checks

$ rails test test/models/shelf_test.rb

Add the presence checks

# app/models/shelf.rb

class Shelf < ApplicationRecord
  belongs_to :food
  belongs_to :owner
  validates :expiry_date, :quantity, presence: true
end
$ rails test test/models/shelf_test.rb

Test scope

# tests/models/shelf_test.rb

[snip]
  test '#expiring_food' do
    refute_includes Shelf.expiring_food, shelves(:high_quantity_and_far_expiry_date)
    refute_includes Shelf.expiring_food, shelves(:low_quantity_and_far_expiry_date)
    assert_includes Shelf.expiring_food, shelves(:high_quantity_and_close_expiry_date)
  end
end
# test/fixtures/shelves.yml

high_quantity_and_far_expiry_date:
  food: two
  owner: two
  expiry_date: <%= 1.week.from_now %>
  quantity: 1

low_quantity_and_far_expiry_date:
  food: one
  owner: one
  expiry_date: <%= 1.week.from_now %>
  quantity: 0

high_quantity_and_close_expiry_date:
  food: two
  owner: two
  expiry_date: <%= 2.days.ago %>
  quantity: 1

Add scope

# app/models/shelf.rb

class Shelf < ApplicationRecord
  belongs_to :food
  belongs_to :owner
  validates :expiry_date, :quantity, presence: true

  scope :expiring_food, -> { where('expiry_date < ?', 3.days.from_now) }
end
$ rails test test/models/shelf_test.rb

Setup guard-minitest (Rails 5)

Add 'guard' and 'guard-minitest' under the development group in the Gemfile

 

Go on the Terminal and install the new gems by running

After the gems are installed, create the boilerplate Guardfile

$ bundle
$ bundle exec guard init minitest

Edit the generated Guardfile by uncommenting, removing and adding rules

Example Guardfile

# Guardfile

guard :minitest, spring: 'bin/rails test' do
  watch(%r{^test/fixtures/(.+)\.yml$})                    { 'test' }
  watch(%r{^app/views/(.+)\.erb$})                        { 'test/integration' }

  # Rails 4
  watch(%r{^app/(.+)\.rb$})                               { |m| "test/#{m[1]}_test.rb" }
  watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' }
  watch(%r{^app/controllers/(.+)_controller\.rb$})        { |m| "test/integration/#{m[1]}_test.rb" }
  watch(%r{^app/views/(.+)_mailer/.+})                    { |m| "test/mailers/#{m[1]}_mailer_test.rb" }
  watch(%r{^lib/(.+)\.rb$})                               { |m| "test/lib/#{m[1]}_test.rb" }
  watch(%r{^test/.+_test\.rb$})
  watch(%r{^test/test_helper\.rb$})                       { 'test' }
end

To have guard start watching, run on a Terminal window or tab

$ bundle exec guard

Resources

All articles on semaphoreci (Rails 5) by Heidar Bernhardsson
https://semaphoreci.com/community/authors/heidar

 

The Minitest Cookbook (Rails 5) by Chris Kottom
https://chriskottom.com/minitestcookbook/

 

Cheatsheet (Rails 5) by Chris Kottom
https://chriskottom.com/blog/2016/08/minitest-cheat-sheet/

 

minitest_and_fixtures

By knockycode

minitest_and_fixtures

  • 80
Loading comments...

More from knockycode