Check out the branch em-solved to see the finished solution used in this repo.
- Confidence in functionality
- Documentation
- Regression testing
- Leads to more manageable code
- Ability to refactor without fear
- Reduced requirement to test manually, which is slower, automated testing is faster
- Saves time in the long run
- Breaks down the problem
- Easier to cover edge cases
- Unit tests: do not involve database connection
- Integration tests: involve the database, thus all the AR models specs are actually integration tests
- to try and make the test faster, we try to build objects, rather than creating and saving them to the db
- Feature tests: system wide under test,browser experience and user workflow, testing multiple models
- creating objects and saving them to the database
- slower tests
- Public not private methods, basically the model public API, and which need to be supported. Private methods can change more. Public ones less
- Golden money path
- As a minimum, models and feature specs
- Although, lately I've been writing more types of specs:
- controller specs: for example posting
paramsthat are impossible to do from the UI, invalid variables - view specs: for example checking that
- presenters specs
- request specs: for example to check redirects
- helper specs
- jobs specs
- mailer specs
- services specs
- controller specs: for example posting
group :development, :test do gem "factory_bot_rails" gem "hirb" gem "pry-byebug" gem "rspec-rails", "5.0.2" end group :test do gem "capybara" gem "cuprite" gem "database_cleaner" gem "launchy" gem "rails-controller-testing" gem "shoulda-matchers", "~> 4.0" gem "timecop" gem "webmock" end-
web-console: access an IRB console on exception pages or by using
<% console %>in views -
Better Errors: better error page for Rack apps
fill_form(:user, { name: "John", email: "john@example.com", "Terms of Service" => true }) click_on submit(:user)RSpec 3.x introduces a new rails_helper.rb convention, which contains all the Rails-specific spec configuration and leaves spec_helper.rb minimal, with no Rails code.
% rails generate rspec:install
- .rspec
- spec/spec_helper.rb
- spec/rails_helper.rb
- spec/
- spec/support/
- spec/factories.rb
- spec/spec_helper.rb
- spec/rails_helper.rb
# http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| config.expect_with :rspec do |expectations| expectations.syntax = :expect end config.mock_with :rspec do |mocks| mocks.syntax = :expect end config.order = :random endENV["RAILS_ENV"] ||= "test" require "spec_helper" require File.expand_path("../../config/environment", __FILE__) require "rspec/rails" Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } ActiveRecord::Migration.maintain_test_schema! RSpec.configure do |config| config.infer_base_class_for_anonymous_controllers = false config.use_transactional_fixtures = false config.infer_spec_type_from_file_location! end# spec/models/user_spec.rb require "rails_helper" describe User do it { is_expected.to have_many(:pets).dependent(:destroy) } it { is_expected.to validate_presence_of(:first_name) } describe "#full_name" do it "returns title cased first and last names" do user = User.new(first_name: "homer", last_name: "simpson") expect(user.full_name).to eq "Homer Simpson" end it "returns only first_name if no last_name" do user = User.new(first_name: "homer") expect(user.full_name).to eq "Homer" end end end# app/models/user.rb class User < ActiveRecord::Base has_many :pets, dependent: :destroy validates :first_name, presence: true def full_name [first_name, last_name].compact.join(" ").titleize end endrequire_relative "../../app/models/a_nice_walk" describe ANiceWalk do context "without a pet" do it "is impossible" do alice = double(pets: []) expect do ANiceWalk.for(alice) end.to raise_error CantWalkWithoutPets end end context "with a pet" do it "does not raise an error" do alice = double(pets: [double]) allow(alice).to receive(:update) expect do ANiceWalk.for(alice) end.not_to raise_error end it "makes the walker happy" do alice = double(pets: [double]) allow(alice).to receive(:update) ANiceWalk.for(alice) expect(alice).to have_received(:update).with(happiness: 20) end end endclass ANiceWalk def self.for(person) raise CantWalkWithoutPets if person.pets.empty? person.update(happiness: 20) end end class CantWalkWithoutPets < StandardError; end- Loading Rails and bundled gems vs just relative loading the only necessary files
binstubs: https://github.com/sstephenson/rbenv/wiki/Understanding-binstubs or http://mislav.uniqpath.com/2013/01/understanding-binstubs/
- Document explicit dependencies through tests
- How to learn TDD without getting overwhelmed
- Shoulda Matchers
- Destroy All Software
- Book: Rails 5 Test Prescriptions
- Book: Testing Rails
- Book: Everyday Rails Testing with RSpec
Some of the code used in this example repo was based on Destroy All Software's screencast number 10: Fast tests with and without Rails