├── 04-acceptance-specs ├── 04 │ ├── json_example.json │ ├── json_example.irbr │ ├── expense_tracker │ │ ├── app │ │ │ └── api.rb │ │ └── spec │ │ │ └── acceptance │ │ │ └── expense_tracker_api_spec.rb │ └── sessions │ │ └── rspec_runs │ │ ├── 2.pml │ │ └── 1.pml ├── 06 │ ├── expense_tracker │ │ ├── .gitignore │ │ └── config.ru │ └── sessions │ │ ├── curl_output.pml │ │ ├── boot_app.pml │ │ └── rspec_runs │ │ └── 1.pml ├── 02 │ ├── expense_tracker │ │ ├── app │ │ │ └── api.rb │ │ └── spec │ │ │ └── acceptance │ │ │ └── expense_tracker_api_spec.rb │ └── sessions │ │ └── rspec_runs │ │ ├── 2.pml │ │ └── 1.pml ├── 03 │ ├── expense_tracker │ │ ├── app │ │ │ └── api.rb │ │ └── spec │ │ │ └── acceptance │ │ │ └── expense_tracker_api_spec.rb │ └── sessions │ │ └── rspec_runs │ │ ├── 2.pml │ │ └── 1.pml ├── 01 │ ├── sessions │ │ ├── initialize_rspec.pml │ │ ├── initialize_bundler.pml │ │ ├── rspec_runs │ │ │ └── 1.pml │ │ └── bundle_install.pml │ └── expense_tracker │ │ ├── Gemfile │ │ └── spec │ │ └── acceptance │ │ └── expense_tracker_api_spec.rb └── 05 │ ├── expense_tracker │ └── app │ │ └── api.rb │ └── sessions │ └── rspec_runs │ ├── 1.pml │ ├── 2.pml │ ├── 3.pml │ └── 4.pml ├── 00-introduction ├── 01 │ └── type_me_in.rb ├── 02 │ └── irb_session.irbr └── 03 │ └── shell_example.pml ├── 01-getting-started ├── 01 │ ├── spec │ │ └── sandwich_spec.rb │ └── sessions │ │ ├── check_rspec_version.pml │ │ └── install_rspec.pml ├── 02 │ ├── spec │ │ └── sandwich_spec.rb │ ├── sandwich_test.rb │ └── sessions │ │ └── rspec_runs │ │ └── 1.pml ├── 03 │ ├── spec │ │ └── sandwich_spec.rb │ └── sessions │ │ └── rspec_runs │ │ └── 1.pml ├── 04 │ ├── sessions │ │ └── rspec_runs │ │ │ └── 1.pml │ └── spec │ │ └── sandwich_spec.rb ├── 05 │ ├── sessions │ │ └── rspec_runs │ │ │ └── 1.pml │ └── spec │ │ └── sandwich_spec.rb ├── 07 │ ├── sessions │ │ └── rspec_runs │ │ │ └── 1.pml │ └── spec │ │ └── sandwich_spec.rb ├── 09 │ ├── sessions │ │ └── rspec_runs │ │ │ └── 1.pml │ └── spec │ │ └── sandwich_spec.rb ├── 08 │ └── toaster.rb └── 06 │ ├── spec │ └── sandwich_spec.rb │ └── sessions │ └── fail_toppings.pml ├── 06-integration-specs ├── 03 │ ├── expense_tracker │ │ ├── config │ │ │ └── sequel.rb │ │ ├── spec │ │ │ ├── support │ │ │ │ └── db.rb │ │ │ └── integration │ │ │ │ └── app │ │ │ │ └── ledger_spec.rb │ │ └── db │ │ │ └── migrations │ │ │ └── 0001_create_expenses.rb │ └── sessions │ │ ├── sequel_migration.out.pml │ │ └── rspec_runs │ │ └── 1.pml ├── 07 │ ├── expense_tracker │ │ └── spec │ │ │ ├── integration │ │ │ └── an_integration_spec.rb │ │ │ └── support │ │ │ └── db.rb │ └── sessions │ │ └── rspec_runs │ │ ├── 3.pml │ │ ├── 4.pml │ │ ├── 5.pml │ │ ├── 1.pml │ │ └── 6.pml ├── 09 │ ├── sessions │ │ ├── grep_config_sequel.pml │ │ ├── curl_failure.pml │ │ ├── rspec_runs │ │ │ ├── 4.pml │ │ │ ├── 2.pml │ │ │ └── 3.pml │ │ ├── curl_output_success.pml │ │ ├── boot_app_success.pml │ │ ├── server_failure.pml │ │ ├── each_spec_file_failure.pml │ │ └── each_spec_file_success.pml │ └── expense_tracker │ │ └── app │ │ └── ledger.rb ├── 01 │ ├── expense_tracker │ │ └── Gemfile │ └── sessions │ │ └── bundle_install.pml ├── 02 │ └── play_with_sequel.irbr ├── 06 │ ├── expense_tracker │ │ └── app │ │ │ └── ledger.rb │ └── sessions │ │ └── rspec_runs │ │ └── 1.pml ├── 08 │ ├── sessions │ │ └── rspec_runs │ │ │ ├── 2.pml │ │ │ └── 1.pml │ └── expense_tracker │ │ └── app │ │ └── ledger.rb ├── 04 │ └── expense_tracker │ │ └── spec │ │ └── integration │ │ └── app │ │ └── ledger_spec.rb └── 05 │ └── expense_tracker │ └── spec │ └── integration │ └── app │ └── ledger_spec.rb ├── 09-configuring-rspec ├── 02 │ ├── configuring_rspec │ │ ├── .rspec │ │ ├── spec │ │ │ └── spec_helper.rb │ │ ├── a_spec.rb │ │ └── rspec │ │ │ └── print_failures_eagerly.rb │ └── sessions │ │ ├── git_clone.pml │ │ └── rspec_runs │ │ └── 1.pml ├── 01 │ ├── configuring_rspec │ │ └── spec │ │ │ └── byebug_spec.rb │ └── sessions │ │ ├── require_byebug.pml │ │ └── rspec_environment_options.txt.pml ├── exercises │ └── fail_if_slower_than_spec.rb ├── 04 │ └── configuring_rspec │ │ ├── rspec_mocks_configuration_spec.rb │ │ ├── mocha_spec.rb │ │ └── expect_with_spec.rb └── 05 │ └── rspec_configure.rb ├── 12-creating-custom-matchers ├── 04 │ ├── example_user_hash.rb │ └── sessions │ │ └── rspec_runs │ │ └── 1.pml ├── 06 │ ├── custom_matcher │ │ ├── spec │ │ │ ├── support │ │ │ │ └── matchers.rb │ │ │ └── initial_account_spec.rb │ │ └── lib │ │ │ └── account.rb │ └── sessions │ │ └── minimal_matcher_failure.pml ├── 01 │ ├── sessions │ │ ├── better_failure_1.pml │ │ ├── original_failure_1.pml │ │ ├── original_failure_2.pml │ │ └── better_failure_2.pml │ └── custom_matcher │ │ └── lib │ │ └── event.rb ├── 07 │ ├── custom_matcher │ │ └── spec │ │ │ └── support │ │ │ └── matchers.rb │ └── sessions │ │ └── improved_matcher_failure.pml ├── 02 │ └── sessions │ │ └── rspec_runs │ │ └── 1.pml ├── 03 │ └── sessions │ │ └── rspec_runs │ │ └── 1.pml ├── solutions │ └── custom_matcher │ │ └── spec │ │ └── support │ │ └── event_matchers.rb ├── 09 │ ├── sessions │ │ └── composed_matcher_failure.pml │ └── custom_matcher │ │ └── spec │ │ ├── composed_account_spec.rb │ │ └── support │ │ └── matchers.rb ├── 08 │ ├── sessions │ │ └── as_of_matcher_failure.pml │ └── custom_matcher │ │ └── spec │ │ ├── support │ │ └── matchers.rb │ │ └── as_of_account_spec.rb ├── 05 │ └── custom_matchers.irbr └── exercises │ └── custom_matcher │ └── spec │ └── event_matchers_spec.rb ├── 15-using-test-doubles-effectively ├── 10 │ └── api_request_tracker │ │ ├── lib │ │ ├── metrics_reporter.rb │ │ ├── endpoint.rb │ │ └── api_request_tracker.rb │ │ └── spec │ │ └── api_request_tracker_spec.rb ├── 11 │ └── api_request_tracker │ │ └── spec │ │ └── api_request_tracker_spec.rb ├── 12 │ └── api_request_tracker │ │ ├── lib │ │ ├── metrics_reporter.rb │ │ └── api_request_tracker.rb │ │ └── spec │ │ └── api_request_tracker_spec.rb ├── 13 │ └── api_request_tracker │ │ └── spec │ │ └── api_request_tracker_spec.rb ├── 14 │ └── api_request_tracker │ │ └── spec │ │ └── api_request_tracker_spec.rb ├── 15 │ └── api_request_tracker │ │ ├── lib │ │ └── api_request_tracker.rb │ │ └── spec │ │ └── api_request_tracker_spec.rb ├── 16 │ └── api_request_tracker │ │ ├── lib │ │ └── api_request_tracker.rb │ │ └── spec │ │ └── api_request_tracker_spec.rb ├── 17 │ └── api_request_tracker │ │ ├── lib │ │ └── api_request_tracker.rb │ │ └── spec │ │ └── api_request_tracker_spec.rb ├── 18 │ └── twitter_user_formatter │ │ ├── lib │ │ └── twitter_user_formatter.rb │ │ └── spec │ │ ├── twitter_user_formatter_spec.rb │ │ └── integration_spec.rb ├── 19 │ └── ruby_doc_server │ │ ├── spec │ │ └── ruby_doc_server_spec.rb │ │ └── lib │ │ └── ruby_doc_server.rb ├── 20 │ └── ruby_doc_server │ │ └── spec │ │ └── ruby_doc_server_spec.rb ├── 21 │ └── sales_tax │ │ ├── Gemfile │ │ └── lib │ │ ├── invoice.rb │ │ └── my_app.rb ├── 22 │ └── sales_tax │ │ ├── spec │ │ ├── integration │ │ │ └── invoice_spec.rb │ │ └── unit │ │ │ └── invoice_spec.rb │ │ └── lib │ │ └── invoice.rb ├── 23 │ └── sales_tax │ │ └── lib │ │ └── sales_tax.rb ├── 24 │ └── sales_tax │ │ ├── spec │ │ ├── unit │ │ │ └── invoice_spec.rb │ │ └── integration │ │ │ └── sales_tax_spec.rb │ │ └── lib │ │ └── invoice.rb ├── 04 │ └── daily_summary_email │ │ ├── lib │ │ ├── email_sender.rb │ │ └── daily_summary.rb │ │ └── spec │ │ └── daily_summary_spec.rb ├── 01 │ ├── password_strength_validator │ │ ├── lib │ │ │ ├── configuration.rb │ │ │ └── password_strength_validator.rb │ │ └── spec │ │ │ └── password_strength_validator_spec.rb │ └── sessions │ │ └── rspec_runs │ │ └── 1.pml ├── 02 │ └── password_strength_validator │ │ ├── lib │ │ └── configuration.rb │ │ └── spec │ │ └── password_strength_validator_spec.rb ├── 06 │ └── subscription_service │ │ ├── spec │ │ ├── spec_helper.rb │ │ └── recurring_payment_spec.rb │ │ └── lib │ │ ├── cash_cow.rb │ │ └── recurring_payment.rb ├── 09 │ └── subscription_service │ │ ├── spec │ │ ├── spec_helper.rb │ │ └── recurring_payment_spec.rb │ │ └── lib │ │ └── recurring_payment.rb ├── 08 │ └── subscription_service │ │ └── lib │ │ └── recurring_payment.rb ├── 03 │ └── daily_summary_email │ │ ├── lib │ │ └── daily_summary.rb │ │ └── spec │ │ └── daily_summary_spec.rb ├── 05 │ └── daily_summary_email │ │ └── spec │ │ └── daily_summary_spec.rb ├── 07 │ └── subscription_service │ │ └── spec │ │ └── recurring_payment_spec.rb ├── exercises │ └── guessing_game │ │ └── lib │ │ └── guessing_game.rb └── solutions │ └── guessing_game │ └── lib │ └── guessing_game.rb ├── A2-using-rspec-with-rails └── 01 │ ├── rails_app │ └── spec │ │ └── models │ │ └── pterodactyl_spec.rb │ └── sessions │ ├── reinstall_binstubs.pml │ ├── install_rspec.pml │ └── generate_pterodactyl.pml ├── 08-metadata ├── 10 │ └── spec │ │ └── music_storage_spec.rb ├── 11 │ └── spec │ │ └── spec_helper.rb ├── 12 │ └── spec │ │ └── spec_helper.rb ├── 13 │ └── random_order.rb ├── 05 │ └── spec │ │ ├── spec_helper.rb │ │ ├── unit │ │ └── unit_spec.rb │ │ └── integration │ │ └── integration_spec.rb ├── 01 │ ├── spec │ │ └── metadata_spec.rb │ └── sessions │ │ └── rspec_runs │ │ └── 1.pml ├── 08 │ └── spec │ │ ├── metadata_spec.rb │ │ ├── spec_helper.rb │ │ └── aggregate_failures_spec.rb ├── 03 │ └── spec │ │ └── metadata_spec.rb ├── 09 │ └── around_hook.rb ├── 02 │ └── spec │ │ └── metadata_spec.rb ├── 04 │ └── spec │ │ ├── metadata_spec.rb │ │ └── metadata_inheritance_spec.rb ├── 06 │ └── spec │ │ └── spec_helper.rb ├── 07 │ └── aggregate_failures.rb └── exercises │ └── expense_tracker │ └── spec │ └── support │ └── db.rb ├── 05-unit-specs ├── 10 │ ├── expense_tracker │ │ └── app │ │ │ └── ledger.rb │ └── sessions │ │ └── rspec_runs │ │ └── 1.pml ├── 11 │ └── expense_tracker │ │ └── spec │ │ └── unit │ │ └── api_example_spec.rb ├── 12 │ ├── expense_tracker │ │ └── app │ │ │ ├── ledger.rb │ │ │ └── api.rb │ └── sessions │ │ └── rspec_runs │ │ └── 3.pml ├── 08 │ └── expense_tracker │ │ └── app │ │ ├── ledger.rb │ │ └── api.rb ├── 09 │ └── expense_tracker │ │ └── app │ │ └── ledger.rb ├── 03 │ ├── sessions │ │ └── rspec_runs │ │ │ ├── 2.pml │ │ │ └── 1.pml │ └── expense_tracker │ │ ├── app │ │ └── api.rb │ │ ├── api_snippets.rb │ │ └── spec │ │ └── unit │ │ └── app │ │ └── api_spec.rb ├── 04 │ ├── expense_tracker │ │ └── app │ │ │ └── api.rb │ └── sessions │ │ └── rspec_runs │ │ ├── 1.pml │ │ └── 2.pml ├── 02 │ ├── expense_tracker │ │ └── spec │ │ │ └── unit │ │ │ └── app │ │ │ └── api_spec.rb │ └── sessions │ │ └── rspec_runs │ │ └── 1.pml └── 07 │ ├── expense_tracker │ └── app │ │ └── api.rb │ └── sessions │ └── rspec_runs │ └── 1.pml ├── A1-rspec-and-wider-ecosystem ├── 02 │ ├── sessions │ │ ├── rake_spec.pml │ │ └── rake_doc_format.pml │ └── expense_tracker │ │ └── Rakefile ├── 01 │ └── sessions │ │ ├── bundle_install_standalone.pml │ │ ├── bundle_install_standalone_binstubs.pml │ │ ├── each_spec_bin_rspec.pml │ │ └── each_spec_bundle_exec.pml └── 03 │ └── minitest_with_rspec │ └── dinosaur_test.rb ├── 13-understanding-test-doubles ├── exercises │ ├── mountain │ │ ├── spec │ │ │ ├── support │ │ │ │ └── verify_doubled_constants.rb │ │ │ └── skier_spec.rb │ │ └── lib │ │ │ ├── trail_map.rb │ │ │ └── skier.rb │ └── sessions │ │ └── rspec_runs │ │ ├── 1.pml │ │ ├── 2.pml │ │ └── 3.pml ├── 03 │ └── stubbed_constants.rb └── 02 │ └── expense_tracker │ ├── spec │ └── unit │ │ └── ledger_double_spec.rb │ └── app │ └── api.rb ├── 10-exploring-rspec-expectations ├── 02 │ ├── should_example.rb │ ├── parts_of_an_expectation.rb │ └── good_failure_messages.rb ├── 01 │ └── expectation_examples.rb ├── 06 │ ├── spec │ │ ├── cookie_recipe_should_spec.rb │ │ ├── cookie_recipe_subject_spec.rb │ │ ├── cookie_recipe_no_doc_strings_spec.rb │ │ └── cookie_recipe_spec.rb │ └── sessions │ │ └── rspec_runs │ │ ├── 1.pml │ │ ├── 3.pml │ │ ├── 4.pml │ │ └── 2.pml ├── 05 │ └── generated_example_descriptions.irbr ├── 03 │ └── how_matchers_work.irbr └── 04 │ └── composing_matchers.rb ├── 07-structuring-code-examples ├── 10 │ └── shared_examples │ │ ├── lib │ │ └── hash_kv_store.rb │ │ └── spec │ │ └── hash_kv_store_spec.rb ├── 11 │ ├── shared_examples │ │ ├── spec │ │ │ ├── hash_kv_store_spec.rb │ │ │ ├── include_examples_spec.rb │ │ │ ├── it_behaves_like_twice_spec.rb │ │ │ ├── include_examples_twice_spec.rb │ │ │ ├── support │ │ │ │ └── kv_store_shared_examples.rb │ │ │ └── pass_block_spec.rb │ │ └── lib │ │ │ └── file_kv_store.rb │ └── sessions │ │ └── rspec_runs │ │ ├── 2.pml │ │ ├── 1.pml │ │ ├── 5.pml │ │ ├── 3.pml │ │ └── 4.pml ├── 04 │ ├── config_hooks_spec.rb │ ├── around_hooks_spec.rb │ ├── spec │ │ └── spec_helper.rb │ └── before_and_after_hooks_spec.rb ├── 05 │ └── expense_tracker │ │ └── spec │ │ └── support │ │ └── db.rb ├── 02 │ └── expense_tracker │ │ └── Gemfile ├── 06 │ └── transit │ │ ├── lib │ │ └── berlin_transit_ticket.rb │ │ └── spec │ │ ├── berlin_transit_ticket_refactored_spec.rb │ │ └── berlin_transit_ticket_spec.rb ├── solutions │ └── shared_examples_exercise │ │ └── spec │ │ ├── uri_spec.rb │ │ ├── addressable_spec.rb │ │ └── support │ │ └── shared_examples.rb ├── exercises │ └── shared_examples_exercise │ │ └── spec │ │ ├── uri_spec.rb │ │ └── addressable_spec.rb └── 03 │ └── expense_tracker │ └── spec │ └── api_spec.rb ├── 02-running-specs ├── 10 │ └── spec │ │ └── coffee_spec.rb ├── 11 │ ├── sessions │ │ └── rspec_runs │ │ │ └── 1.pml │ └── spec │ │ └── coffee_spec.rb ├── 02 │ └── sessions │ │ ├── install_coderay.pml │ │ └── rspec_runs │ │ └── 1.pml ├── 03 │ ├── spec │ │ └── slow_spec.rb │ └── sessions │ │ └── rspec_runs │ │ └── 1.pml ├── 06 │ ├── sessions │ │ └── rspec_runs │ │ │ ├── 1.pml │ │ │ ├── 5.pml │ │ │ ├── 6.pml │ │ │ ├── 2.pml │ │ │ ├── 3.pml │ │ │ └── 4.pml │ └── spec │ │ └── coffee_spec.rb ├── 07 │ ├── sessions │ │ └── rspec_runs │ │ │ └── 1.pml │ └── spec │ │ └── coffee_spec.rb ├── 08 │ ├── sessions │ │ └── rspec_runs │ │ │ └── 1.pml │ └── spec │ │ └── coffee_spec.rb ├── exercises │ └── spec │ │ └── tea_spec.rb ├── 05 │ └── sessions │ │ └── rspec_runs │ │ ├── 1.pml │ │ └── 2.pml ├── solutions │ ├── 01 │ │ └── spec │ │ │ └── tea_spec.rb │ └── 02 │ │ └── spec │ │ └── tea_spec.rb ├── 01 │ ├── spec │ │ └── coffee_spec.rb │ └── sessions │ │ └── rspec_runs │ │ ├── 1.pml │ │ └── 2.pml ├── 09 │ ├── sessions │ │ └── rspec_runs │ │ │ └── 1.pml │ └── spec │ │ └── coffee_spec.rb └── 04 │ └── sessions │ └── rspec_runs │ └── 1.pml ├── 11-matchers-included-in-rspec-expectations ├── exercises │ ├── water_spec.rb │ ├── calendar_spec.rb │ ├── tokenizer_spec.rb │ ├── phone_number_extractor_spec.rb │ ├── sessions │ │ ├── calendar_spec.pml │ │ ├── water_spec.pml │ │ └── phone_number_extractor_spec.pml │ └── public_company_spec.rb └── solutions │ ├── water_spec.rb │ ├── calendar_spec.rb │ ├── tokenizer_spec.rb │ ├── phone_number_extractor_spec.rb │ ├── sessions │ ├── calendar_spec.pml │ ├── water_spec.pml │ └── tokenizer_spec.pml │ └── public_company_spec.rb ├── A3-matcher-cheat-sheet └── 01 │ └── matchers_cheat_sheet.rb └── 14-customizing-test-doubles ├── 01 └── and_return.irbr └── 02 └── stock_ticker.rb /04-acceptance-specs/04/json_example.json: -------------------------------------------------------------------------------- 1 | { "expense_id": 42 } 2 | -------------------------------------------------------------------------------- /00-introduction/01/type_me_in.rb: -------------------------------------------------------------------------------- 1 | puts "You can type me in; it's okay!" 2 | -------------------------------------------------------------------------------- /04-acceptance-specs/06/expense_tracker/.gitignore: -------------------------------------------------------------------------------- 1 | spec/examples.txt 2 | db/*.db 3 | bin 4 | bundle 5 | log 6 | -------------------------------------------------------------------------------- /04-acceptance-specs/06/expense_tracker/config.ru: -------------------------------------------------------------------------------- 1 | require_relative 'app/api' 2 | run ExpenseTracker::API.new 3 | -------------------------------------------------------------------------------- /01-getting-started/01/spec/sandwich_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | RSpec.describe 'An ideal sandwich' do 3 | it 'is delicious' do 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /00-introduction/02/irb_session.irbr: -------------------------------------------------------------------------------- 1 | >> %w[Type in just the bit after the prompt].join(' ') 2 | => "Type in just the bit after the prompt" 3 | -------------------------------------------------------------------------------- /04-acceptance-specs/04/json_example.irbr: -------------------------------------------------------------------------------- 1 | >> require 'json' 2 | => true 3 | >> JSON.parse('{ "expense_id": 42 }') 4 | => {"expense_id"=>42} 5 | -------------------------------------------------------------------------------- /06-integration-specs/03/expense_tracker/config/sequel.rb: -------------------------------------------------------------------------------- 1 | require 'sequel' 2 | DB = Sequel.sqlite("./db/#{ENV.fetch('RACK_ENV', 'development')}.db") 3 | -------------------------------------------------------------------------------- /09-configuring-rspec/02/configuring_rspec/.rspec: -------------------------------------------------------------------------------- 1 | -I<%= ENV['HOME'] %>/rspec-print_failures_eagerly/lib 2 | --require 'rspec/print_failures_eagerly' 3 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/04/example_user_hash.rb: -------------------------------------------------------------------------------- 1 | # validate me 2 | 3 | { 4 | id: 1, 5 | email: 'john.doe@example.com', 6 | role: 'admin' 7 | } 8 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/10/api_request_tracker/lib/metrics_reporter.rb: -------------------------------------------------------------------------------- 1 | class MetricsReporter 2 | def increment(metric_name) 3 | # ... 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /A2-using-rspec-with-rails/01/rails_app/spec/models/pterodactyl_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Pterodactyl, type: :model do 4 | # ... 5 | end 6 | -------------------------------------------------------------------------------- /04-acceptance-specs/02/expense_tracker/app/api.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require 'json' 3 | 4 | module ExpenseTracker 5 | class API < Sinatra::Base 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/12/api_request_tracker/lib/metrics_reporter.rb: -------------------------------------------------------------------------------- 1 | class MetricsReporter 2 | def self.increment(metric_name) 3 | # ... 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /08-metadata/05/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.define_derived_metadata(file_path: /spec\/unit/) do |meta| 3 | meta[:fast] = true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /05-unit-specs/08/expense_tracker/app/ledger.rb: -------------------------------------------------------------------------------- 1 | module ExpenseTracker 2 | RecordResult = Struct.new(:success?, :expense_id, :error_message) 3 | 4 | class Ledger 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /08-metadata/01/spec/metadata_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pp' 2 | 3 | RSpec.describe Hash do 4 | it 'is used by RSpec for metadata' do |example| 5 | pp example.metadata 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /08-metadata/08/spec/metadata_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pp' 2 | 3 | RSpec.describe Hash do 4 | it 'is used by RSpec for metadata' do |example| 5 | pp example.metadata 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /06-integration-specs/07/expense_tracker/spec/integration/an_integration_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../support/db' 2 | 3 | RSpec.describe 'An integration spec', :db do 4 | # ... 5 | end 6 | -------------------------------------------------------------------------------- /08-metadata/03/spec/metadata_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pp' 2 | 3 | RSpec.describe Hash do 4 | it 'is used by RSpec for metadata', :fast do |example| 5 | pp example.metadata 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /08-metadata/09/around_hook.rb: -------------------------------------------------------------------------------- 1 | # validate me 2 | require 'rspec' 3 | 4 | RSpec.configure do |config| 5 | config.around(:example) do |example| 6 | pp example.metadata 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /09-configuring-rspec/01/configuring_rspec/spec/byebug_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Byebug' do 2 | it 'can use the debugger' do 3 | expect(self).to respond_to(:debugger) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /08-metadata/02/spec/metadata_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pp' 2 | 3 | RSpec.describe Hash do 4 | it 'is used by RSpec for metadata', fast: true do |example| 5 | pp example.metadata 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /08-metadata/05/spec/unit/unit_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'A unit spec' do 2 | it 'gets `fast: true` metadata' do |example| 3 | expect(example.metadata).to include(fast: true) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/06/custom_matcher/spec/support/matchers.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :have_a_balance_of do |amount| 2 | match { |account| account.current_balance == amount } 3 | end 4 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/04/daily_summary_email/lib/email_sender.rb: -------------------------------------------------------------------------------- 1 | class EmailSender 2 | def deliver(email:, subject:, body:) 3 | # send the message via SMTP 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /08-metadata/04/spec/metadata_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pp' 2 | 3 | RSpec.describe Hash do 4 | it 'is used by RSpec for metadata', :fast, :focus do |example| 5 | pp example.metadata 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/01/password_strength_validator/lib/configuration.rb: -------------------------------------------------------------------------------- 1 | module Acme 2 | module Config 3 | def self.min_password_length 4 | 12 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/02/password_strength_validator/lib/configuration.rb: -------------------------------------------------------------------------------- 1 | module Acme 2 | module Config 3 | def self.min_password_length 4 | 12 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /08-metadata/13/random_order.rb: -------------------------------------------------------------------------------- 1 | # validate me 2 | require 'rspec' 3 | 4 | class SomeNewExampleGroup 5 | end 6 | 7 | RSpec.describe SomeNewExampleGroup, order: :random do 8 | # ... 9 | end 10 | -------------------------------------------------------------------------------- /08-metadata/08/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.define_derived_metadata do |meta| 3 | meta[:aggregate_failures] = true unless meta.key?(:aggregate_failures) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/06/subscription_service/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.mock_with :rspec do |mocks| 3 | mocks.verify_partial_doubles = true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/09/subscription_service/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.mock_with :rspec do |mocks| 3 | mocks.verify_partial_doubles = true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /A1-rspec-and-wider-ecosystem/02/sessions/rake_spec.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rake spec 5 | 6 | -------------------------------------------------------------------------------- /05-unit-specs/09/expense_tracker/app/ledger.rb: -------------------------------------------------------------------------------- 1 | module ExpenseTracker 2 | RecordResult = Struct.new(:success?, :expense_id, :error_message) 3 | 4 | class Ledger 5 | def record 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /09-configuring-rspec/01/sessions/require_byebug.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec -rbyebug 5 | 6 | -------------------------------------------------------------------------------- /13-understanding-test-doubles/exercises/mountain/spec/support/verify_doubled_constants.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |c| 2 | c.mock_with :rspec do |mocks| 3 | mocks.verify_doubled_constant_names = true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /13-understanding-test-doubles/exercises/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec 5 | 6 | -------------------------------------------------------------------------------- /00-introduction/03/shell_example.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ echo 'RSpec is great!' 5 | RSpec is great! 6 | 7 | -------------------------------------------------------------------------------- /04-acceptance-specs/03/expense_tracker/app/api.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require 'json' 3 | 4 | module ExpenseTracker 5 | class API < Sinatra::Base 6 | post '/expenses' do 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /10-exploring-rspec-expectations/02/should_example.rb: -------------------------------------------------------------------------------- 1 | # validate me 2 | require 'rspec/expectations' 3 | include RSpec::Matchers 4 | 5 | RSpec::Expectations.configuration.syntax = :should 6 | 7 | 'food'.should match(/foo/) 8 | -------------------------------------------------------------------------------- /08-metadata/05/spec/integration/integration_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'An integration spec' do 2 | it 'does not get `fast: true` metadata' do |example| 3 | expect(example.metadata).not_to include(fast: true) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /09-configuring-rspec/exercises/fail_if_slower_than_spec.rb: -------------------------------------------------------------------------------- 1 | # validate me 2 | require 'rspec/core' 3 | 4 | SomeFastUnitSpecs = Class.new 5 | 6 | RSpec.describe SomeFastUnitSpecs, fail_if_slower_than: 0.01 do 7 | # ... 8 | end 9 | -------------------------------------------------------------------------------- /13-understanding-test-doubles/exercises/sessions/rspec_runs/2.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec -rtrail_map 5 | 6 | -------------------------------------------------------------------------------- /05-unit-specs/10/expense_tracker/app/ledger.rb: -------------------------------------------------------------------------------- 1 | module ExpenseTracker 2 | RecordResult = Struct.new(:success?, :expense_id, :error_message) 3 | 4 | class Ledger 5 | def record(expense) 6 | end 7 | 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /07-structuring-code-examples/11/shared_examples/spec/hash_kv_store_spec.rb: -------------------------------------------------------------------------------- 1 | require 'hash_kv_store' 2 | require 'support/kv_store_shared_examples' 3 | 4 | RSpec.describe HashKVStore do 5 | it_behaves_like 'KV store', HashKVStore 6 | end 7 | -------------------------------------------------------------------------------- /A1-rspec-and-wider-ecosystem/02/sessions/rake_doc_format.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rake spec:unit SPEC_OPTS='-fd' 5 | 6 | -------------------------------------------------------------------------------- /06-integration-specs/03/expense_tracker/spec/support/db.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |c| 2 | c.before(:suite) do 3 | Sequel.extension :migration 4 | Sequel::Migrator.run(DB, 'db/migrations') 5 | DB[:expenses].truncate 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /07-structuring-code-examples/11/shared_examples/spec/include_examples_spec.rb: -------------------------------------------------------------------------------- 1 | require 'hash_kv_store' 2 | require 'support/kv_store_shared_examples' 3 | 4 | RSpec.describe HashKVStore do 5 | include_examples 'KV store', HashKVStore 6 | end 7 | -------------------------------------------------------------------------------- /A1-rspec-and-wider-ecosystem/01/sessions/bundle_install_standalone.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle install --standalone 5 | 6 | -------------------------------------------------------------------------------- /04-acceptance-specs/06/sessions/curl_output.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ curl localhost:9292/expenses/2017-06-10 -w "\n" 5 | [] 6 | 7 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/10/api_request_tracker/lib/endpoint.rb: -------------------------------------------------------------------------------- 1 | Request = Struct.new(:verb, :path) 2 | 3 | class Endpoint 4 | def self.description_of(request) 5 | "#{request.verb}_#{request.path.sub(%r{^/}, '')}" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /09-configuring-rspec/04/configuring_rspec/rspec_mocks_configuration_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.mock_with :rspec do |mocks| 3 | mocks.verify_partial_doubles = true 4 | mocks.verify_doubled_constant_names = true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /A2-using-rspec-with-rails/01/sessions/reinstall_binstubs.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle config --delete bin 5 | $ rails app:update:bin 6 | 7 | -------------------------------------------------------------------------------- /13-understanding-test-doubles/exercises/sessions/rspec_runs/3.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec -rtrail_map -rsupport/verify_doubled_constants 5 | 6 | -------------------------------------------------------------------------------- /A1-rspec-and-wider-ecosystem/01/sessions/bundle_install_standalone_binstubs.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle install --standalone --binstubs 5 | 6 | -------------------------------------------------------------------------------- /04-acceptance-specs/04/expense_tracker/app/api.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require 'json' 3 | 4 | module ExpenseTracker 5 | class API < Sinatra::Base 6 | post '/expenses' do 7 | JSON.generate('expense_id' => 42) 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /09-configuring-rspec/02/configuring_rspec/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | class MyFormatter 2 | RSpec::Core::Formatters.register self 3 | 4 | def initialize(_) 5 | end 6 | end 7 | 8 | RSpec.configure do |config| 9 | config.add_formatter MyFormatter 10 | end 11 | -------------------------------------------------------------------------------- /09-configuring-rspec/02/sessions/git_clone.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ git clone https://github.com/rspec-3-book/rspec-print_failures_eagerly.git 5 | 6 | -------------------------------------------------------------------------------- /01-getting-started/02/spec/sandwich_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | RSpec.describe 'An ideal sandwich' do 3 | it 'is delicious' do 4 | sandwich = Sandwich.new('delicious', []) 5 | 6 | taste = sandwich.taste 7 | 8 | expect(taste).to eq('delicious') 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /04-acceptance-specs/01/sessions/initialize_rspec.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec --init 5 | create .rspec 6 | create spec/spec_helper.rb 7 | 8 | -------------------------------------------------------------------------------- /02-running-specs/02/sessions/install_coderay.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ gem install coderay -v 1.1.1 5 | Successfully installed coderay-1.1.1 6 | 1 gem installed 7 | 8 | -------------------------------------------------------------------------------- /05-unit-specs/12/expense_tracker/app/ledger.rb: -------------------------------------------------------------------------------- 1 | module ExpenseTracker 2 | RecordResult = Struct.new(:success?, :expense_id, :error_message) 3 | 4 | class Ledger 5 | def record(expense) 6 | end 7 | 8 | def expenses_on(date) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /06-integration-specs/03/expense_tracker/db/migrations/0001_create_expenses.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | change do 3 | create_table :expenses do 4 | primary_key :id 5 | String :payee 6 | Float :amount 7 | Date :date 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /07-structuring-code-examples/10/shared_examples/lib/hash_kv_store.rb: -------------------------------------------------------------------------------- 1 | class HashKVStore 2 | def initialize 3 | @hash = {} 4 | end 5 | 6 | def store(key, value) 7 | @hash[key] = value 8 | end 9 | 10 | def fetch(key) 11 | @hash.fetch(key) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /13-understanding-test-doubles/exercises/mountain/lib/trail_map.rb: -------------------------------------------------------------------------------- 1 | puts 'Loading our database query library...' 2 | sleep(1) 3 | 4 | module Mountain 5 | class TrailMap 6 | def difficulty_of(trail_name) 7 | # Look up the trail in the database 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/01/sessions/better_failure_1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |  expected # to have no tickets sold, but ↩ 5 | had 2 6 | 7 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/01/sessions/original_failure_1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |  expected: 0 5 |  got: 2 6 |  7 |  (compared using ==) 8 | 9 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/18/twitter_user_formatter/lib/twitter_user_formatter.rb: -------------------------------------------------------------------------------- 1 | require 'twitter' 2 | 3 | class TwitterUserFormatter 4 | def initialize(user) 5 | @user = user 6 | end 7 | 8 | def format 9 | @user.name + "'s website is " + @user.url 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /11-matchers-included-in-rspec-expectations/exercises/water_spec.rb: -------------------------------------------------------------------------------- 1 | class Water 2 | def self.elements 3 | [:oxygen, :hydrogen] 4 | end 5 | end 6 | 7 | RSpec.describe Water do 8 | it 'is H2O' do 9 | expect(Water.elements.sort).to eq [:hydrogen, :hydrogen, :oxygen] 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /11-matchers-included-in-rspec-expectations/solutions/water_spec.rb: -------------------------------------------------------------------------------- 1 | class Water 2 | def self.elements 3 | [:oxygen, :hydrogen] 4 | end 5 | end 6 | 7 | RSpec.describe Water do 8 | it 'is H2O' do 9 | expect(Water.elements).to contain_exactly(:hydrogen, :hydrogen, :oxygen) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/01/sessions/original_failure_2.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |  expected: 10000 5 |  got: 9900 6 |  7 |  (compared using ==) 8 | 9 | -------------------------------------------------------------------------------- /01-getting-started/03/spec/sandwich_spec.rb: -------------------------------------------------------------------------------- 1 | Sandwich = Struct.new(:taste, :toppings) 2 | 3 | RSpec.describe 'An ideal sandwich' do 4 | it 'is delicious' do 5 | sandwich = Sandwich.new('delicious', []) 6 | 7 | taste = sandwich.taste 8 | 9 | expect(taste).to eq('delicious') 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /04-acceptance-specs/01/expense_tracker/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source "https://rubygems.org" 3 | 4 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 5 | 6 | gem 'rspec', '3.6.0' 7 | gem 'coderay', '1.1.1' 8 | gem 'rack-test', '0.7.0' 9 | gem 'sinatra', '2.0.0' 10 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/01/sessions/better_failure_2.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |  expected # to be sold out, but had ↩ 5 | 100 unsold tickets 6 | 7 | -------------------------------------------------------------------------------- /07-structuring-code-examples/11/shared_examples/spec/it_behaves_like_twice_spec.rb: -------------------------------------------------------------------------------- 1 | require 'hash_kv_store' 2 | require 'file_kv_store' 3 | require 'support/kv_store_shared_examples' 4 | 5 | RSpec.describe 'Key-value stores' do 6 | it_behaves_like 'KV store', HashKVStore 7 | it_behaves_like 'KV store', FileKVStore 8 | end 9 | -------------------------------------------------------------------------------- /01-getting-started/03/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec 5 | . 6 | 7 | Finished in 0.00101 seconds (files took 0.08408 seconds to load) 8 | 1 example, 0 failures 9 | 10 | -------------------------------------------------------------------------------- /07-structuring-code-examples/11/shared_examples/spec/include_examples_twice_spec.rb: -------------------------------------------------------------------------------- 1 | require 'hash_kv_store' 2 | require 'file_kv_store' 3 | require 'support/kv_store_shared_examples' 4 | 5 | RSpec.describe 'Key-value stores' do 6 | include_examples 'KV store', HashKVStore 7 | include_examples 'KV store', FileKVStore 8 | end 9 | -------------------------------------------------------------------------------- /10-exploring-rspec-expectations/02/parts_of_an_expectation.rb: -------------------------------------------------------------------------------- 1 | # validate me 2 | require 'rspec/expectations' 3 | include RSpec::Matchers 4 | 5 | class Deck 6 | def cards 7 | @cards ||= Array.new(52) 8 | end 9 | end 10 | 11 | deck = Deck.new 12 | expect(deck.cards.count).to eq(52), 'not playing with a full deck' 13 | -------------------------------------------------------------------------------- /02-running-specs/03/spec/slow_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'The sleep() method' do 2 | it('can sleep for 0.1 second') { sleep 0.1 } 3 | it('can sleep for 0.2 second') { sleep 0.2 } 4 | it('can sleep for 0.3 second') { sleep 0.3 } 5 | it('can sleep for 0.4 second') { sleep 0.4 } 6 | it('can sleep for 0.5 second') { sleep 0.5 } 7 | end 8 | -------------------------------------------------------------------------------- /06-integration-specs/09/sessions/grep_config_sequel.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ grep config/sequel -r . --exclude-dir=.git 5 | ./spec/integration/app/ledger_spec.rb:require_relative '../../../config ↩ 6 | /sequel' 7 | 8 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/21/sales_tax/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source "https://rubygems.org" 3 | 4 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 5 | 6 | gem 'rspec', '3.6.0' 7 | gem 'coderay', '1.1.1' 8 | gem 'taxjar-ruby', '1.7.0' 9 | gem 'dotenv', '2.2.1' 10 | -------------------------------------------------------------------------------- /01-getting-started/04/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec 5 | .. 6 | 7 | Finished in 0.00201 seconds (files took 0.09252 seconds to load) 8 | 2 examples, 0 failures 9 | 10 | -------------------------------------------------------------------------------- /01-getting-started/05/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec 5 | .. 6 | 7 | Finished in 0.00206 seconds (files took 0.08537 seconds to load) 8 | 2 examples, 0 failures 9 | 10 | -------------------------------------------------------------------------------- /01-getting-started/07/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec 5 | .. 6 | 7 | Finished in 0.00191 seconds (files took 0.08132 seconds to load) 8 | 2 examples, 0 failures 9 | 10 | -------------------------------------------------------------------------------- /01-getting-started/09/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec 5 | .. 6 | 7 | Finished in 0.00212 seconds (files took 0.08548 seconds to load) 8 | 2 examples, 0 failures 9 | 10 | -------------------------------------------------------------------------------- /04-acceptance-specs/02/sessions/rspec_runs/2.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec 5 | . 6 | 7 | Finished in 0.01528 seconds (files took 0.1276 seconds to load) 8 | 1 example, 0 failures 9 | 10 | -------------------------------------------------------------------------------- /04-acceptance-specs/03/sessions/rspec_runs/2.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec 5 | . 6 | 7 | Finished in 0.01466 seconds (files took 0.131 seconds to load) 8 | 1 example, 0 failures 9 | 10 | -------------------------------------------------------------------------------- /04-acceptance-specs/04/sessions/rspec_runs/2.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec 5 | . 6 | 7 | Finished in 0.0183 seconds (files took 0.13128 seconds to load) 8 | 1 example, 0 failures 9 | 10 | -------------------------------------------------------------------------------- /04-acceptance-specs/05/expense_tracker/app/api.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require 'json' 3 | 4 | module ExpenseTracker 5 | class API < Sinatra::Base 6 | post '/expenses' do 7 | JSON.generate('expense_id' => 42) 8 | end 9 | 10 | get '/expenses/:date' do 11 | JSON.generate([]) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /04-acceptance-specs/05/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec 5 | . 6 | 7 | Finished in 0.01998 seconds (files took 0.13262 seconds to load) 8 | 1 example, 0 failures 9 | 10 | -------------------------------------------------------------------------------- /04-acceptance-specs/05/sessions/rspec_runs/2.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec 5 | . 6 | 7 | Finished in 0.01843 seconds (files took 0.13757 seconds to load) 8 | 1 example, 0 failures 9 | 10 | -------------------------------------------------------------------------------- /01-getting-started/01/sessions/check_rspec_version.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec --version 5 | RSpec 3.6 6 | - rspec-core 3.6.0 7 | - rspec-expectations 3.6.0 8 | - rspec-mocks 3.6.0 9 | - rspec-support 3.6.0 10 | 11 | -------------------------------------------------------------------------------- /02-running-specs/06/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec --only-failures 5 | 6 | To use `--only-failures`, you must first set ↩ 7 | `config.example_status_persistence_file_path`. 8 | 9 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/01/custom_matcher/lib/event.rb: -------------------------------------------------------------------------------- 1 | Event = Struct.new(:name, :capacity) do 2 | def purchase_ticket_for(guest) 3 | tickets_sold << guest 4 | end 5 | 6 | def tickets_sold 7 | @tickets_sold ||= [] 8 | end 9 | 10 | def inspect 11 | "#" 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/12/api_request_tracker/lib/api_request_tracker.rb: -------------------------------------------------------------------------------- 1 | require 'metrics_reporter' 2 | require 'endpoint' 3 | 4 | class APIRequestTracker 5 | def process(request) 6 | endpoint_description = Endpoint.description_of(request) 7 | MetricsReporter.increment("api.requests.#{endpoint_description}") 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /04-acceptance-specs/01/sessions/initialize_bundler.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ gem install bundler 5 | Successfully installed bundler-1.15.3 6 | 1 gem installed 7 | $ bundle init 8 | Writing new Gemfile to ~/code/expense_tracker/Gemfile 9 | 10 | -------------------------------------------------------------------------------- /02-running-specs/07/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec 5 | Run options: include {:focus=>true} 6 | . 7 | 8 | Finished in 0.00093 seconds (files took 0.07915 seconds to load) 9 | 1 example, 0 failures 10 | 11 | -------------------------------------------------------------------------------- /02-running-specs/08/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec 5 | Run options: include {:focus=>true} 6 | . 7 | 8 | Finished in 0.00087 seconds (files took 0.0791 seconds to load) 9 | 1 example, 0 failures 10 | 11 | -------------------------------------------------------------------------------- /06-integration-specs/07/expense_tracker/spec/support/db.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |c| 2 | c.before(:suite) do 3 | Sequel.extension :migration 4 | Sequel::Migrator.run(DB, 'db/migrations') 5 | DB[:expenses].truncate 6 | end 7 | 8 | c.around(:example, :db) do |example| 9 | DB.transaction(rollback: :always) { example.run } 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /08-metadata/04/spec/metadata_inheritance_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pp' 2 | 3 | RSpec.describe Hash, :outer_group do 4 | it 'is used by RSpec for metadata', :fast, :focus do |example| 5 | pp example.metadata 6 | end 7 | 8 | context 'on a nested group' do 9 | it 'is also inherited' do |example| 10 | pp example.metadata 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/15/api_request_tracker/lib/api_request_tracker.rb: -------------------------------------------------------------------------------- 1 | require 'metrics_reporter' 2 | require 'endpoint' 3 | 4 | class APIRequestTracker 5 | def process(request, reporter: MetricsReporter) 6 | endpoint_description = Endpoint.description_of(request) 7 | reporter.increment("api.requests.#{endpoint_description}") 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /06-integration-specs/01/expense_tracker/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source "https://rubygems.org" 3 | 4 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 5 | 6 | gem 'rspec', '3.6.0' 7 | gem 'coderay', '1.1.1' 8 | gem 'rack-test', '0.7.0' 9 | gem 'sinatra', '2.0.0' 10 | gem 'sequel', '4.48.0' 11 | gem 'sqlite3', '1.3.13' 12 | -------------------------------------------------------------------------------- /07-structuring-code-examples/04/config_hooks_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module MyApp 4 | class Configuration 5 | end 6 | end 7 | 8 | RSpec.describe MyApp::Configuration do 9 | it 'can mutate env' do 10 | ENV['FOO'] = '1' 11 | end 12 | 13 | it 'isolates mutations from other examples' do 14 | expect(ENV['FOO']).to eq nil 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /10-exploring-rspec-expectations/01/expectation_examples.rb: -------------------------------------------------------------------------------- 1 | # validate me 2 | require 'rspec/expectations' 3 | include RSpec::Matchers 4 | 5 | ratio = 22 / 7.0 6 | expect(ratio).to be_within(0.1).of(Math::PI) 7 | 8 | numbers = [13, 3, 99] 9 | expect(numbers).to all be_odd 10 | 11 | alphabet = ('a'..'z').to_a 12 | expect(alphabet).to start_with('a').and end_with('z') 13 | -------------------------------------------------------------------------------- /A3-matcher-cheat-sheet/01/matchers_cheat_sheet.rb: -------------------------------------------------------------------------------- 1 | expect(a).to matcher 2 | 3 | expect(a).not_to matcher 4 | # or 5 | expect(a).to_not matcher 6 | 7 | expect { some_code }.to matcher 8 | # 9 | expect { do_something }.to change(obj, :attr) 10 | # or 11 | expect { do_something }.to change { obj.attr } 12 | 13 | expect { |probe| obj.some_method(&probe) }.to yield_control 14 | -------------------------------------------------------------------------------- /08-metadata/06/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.define_derived_metadata(type: :model) do |meta| 3 | # ... 4 | meta[:matched_by_type_model] = true 5 | end 6 | end 7 | 8 | RSpec.describe do 9 | it 'matches type: :model', type: :model do |example| 10 | expect(example.metadata).to include(matched_by_type_model: true) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/06/subscription_service/lib/cash_cow.rb: -------------------------------------------------------------------------------- 1 | class CashCow 2 | def self.charge_card(card, amount) 3 | # ... 4 | puts "charged card: #{card}" 5 | end 6 | 7 | def self.bulk_charge_cards(cards_and_amounts) 8 | # ... 9 | cards_and_amounts.each do |card, _| 10 | puts "charged card: #{card}" 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /01-getting-started/02/sandwich_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | 3 | Sandwich = Struct.new(:taste, :toppings) 4 | 5 | class TestSandwich < Minitest::Test 6 | def test_that_sandwich_is_delicious 7 | sandwich = Sandwich.new('delicious', []) 8 | 9 | taste = sandwich.taste 10 | 11 | assert_equal('delicious', taste, 'Sandwich is not delicious') 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/10/api_request_tracker/lib/api_request_tracker.rb: -------------------------------------------------------------------------------- 1 | require 'metrics_reporter' 2 | require 'endpoint' 3 | 4 | class APIRequestTracker 5 | def process(request) 6 | endpoint_description = Endpoint.description_of(request) 7 | reporter = MetricsReporter.new 8 | reporter.increment("api.requests.#{endpoint_description}") 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /06-integration-specs/02/play_with_sequel.irbr: -------------------------------------------------------------------------------- 1 | >> require 'sequel' 2 | => true 3 | >> DB = Sequel.sqlite 4 | => #:sqlite}> 5 | >> DB.create_table(:gems) { String :name } 6 | => nil 7 | >> DB[:gems].insert(name: 'rspec') 8 | => 1 9 | >> DB[:gems].insert(name: 'sinatra') 10 | => 2 11 | >> DB[:gems].all 12 | => [{:name=>"rspec"}, {:name=>"sinatra"}] 13 | -------------------------------------------------------------------------------- /07-structuring-code-examples/05/expense_tracker/spec/support/db.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |c| 2 | # ... 3 | 4 | c.before(:suite) do 5 | Sequel.extension :migration 6 | Sequel::Migrator.run(DB, 'db/migrations') 7 | DB[:expenses].truncate 8 | end 9 | 10 | c.around(:example, :db) do |example| 11 | DB.transaction(rollback: :always) { example.run } 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /13-understanding-test-doubles/03/stubbed_constants.rb: -------------------------------------------------------------------------------- 1 | # validate me 2 | require 'rspec/expectations' 3 | include RSpec::Matchers 4 | require 'rspec/mocks/standalone' 5 | 6 | class PasswordHash 7 | COST_FACTOR = 12 8 | 9 | # ... 10 | end 11 | 12 | stub_const('PasswordHash::COST_FACTOR', 1) 13 | 14 | expect(PasswordHash::COST_FACTOR).to eq 1 15 | 16 | hide_const('ActiveRecord') 17 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/06/subscription_service/lib/recurring_payment.rb: -------------------------------------------------------------------------------- 1 | require 'cash_cow' 2 | 3 | class RecurringPayment 4 | def self.process_subscriptions(subscriptions) 5 | subscriptions.each do |subscription| 6 | CashCow.charge_card(subscription.credit_card, subscription.amount) 7 | # ...send receipt and other stuff... 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /02-running-specs/06/sessions/rspec_runs/5.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec --only-failures 5 | Run options: include {:last_run_status=>"failed"} 6 | . 7 | 8 | Finished in 0.00094 seconds (files took 0.09055 seconds to load) 9 | 1 example, 0 failures 10 | 11 | -------------------------------------------------------------------------------- /08-metadata/12/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.filter_run_when_matching :focus 3 | end 4 | 5 | RSpec.describe "With no examples focused" do 6 | it "only runs if no other examples are focused" do 7 | end 8 | end 9 | 10 | RSpec.describe "With an example focused" do 11 | it "runs alone when focused", :focus => !!ENV['FOCUS_AN_EXAMPLE'] do 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /01-getting-started/08/toaster.rb: -------------------------------------------------------------------------------- 1 | # validate me 2 | 3 | Toaster = Struct.new(:serial) do 4 | def self.find_by_serial(serial) 5 | new(serial) 6 | end 7 | end 8 | 9 | def current_toaster 10 | @current_toaster ||= Toaster.find_by_serial('HHGG42') 11 | end 12 | 13 | current_toaster # to ensure it does not raise errors 14 | 15 | @current_toaster = nil || Toaster.find_by_serial('HHGG42') 16 | -------------------------------------------------------------------------------- /09-configuring-rspec/01/sessions/rspec_environment_options.txt.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -I PATH Specify PATH to add to $LOAD_PATH (may ↩ 5 | be used more than once). 6 | -r, --require PATH Require a file. 7 | 8 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/09/subscription_service/lib/recurring_payment.rb: -------------------------------------------------------------------------------- 1 | require 'cash_cow' 2 | 3 | class RecurringPayment 4 | def self.process_subscriptions(subscriptions, bank: CashCow) 5 | subscriptions.each do |subscription| 6 | bank.charge_card(subscription.credit_card, subscription.amount) 7 | # ...send receipt and other stuff... 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /06-integration-specs/06/expense_tracker/app/ledger.rb: -------------------------------------------------------------------------------- 1 | module ExpenseTracker 2 | RecordResult = Struct.new(:success?, :expense_id, :error_message) 3 | 4 | class Ledger 5 | def record(expense) 6 | DB[:expenses].insert(expense) 7 | id = DB[:expenses].max(:id) 8 | RecordResult.new(true, id, nil) 9 | end 10 | 11 | def expenses_on(date) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /08-metadata/08/spec/aggregate_failures_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ':aggregate_failures' do 2 | it 'has :aggregate_failures by default' do |example| 3 | expect(example.metadata).to include(aggregate_failures: true) 4 | end 5 | 6 | it 'can disable :aggregate_failures', aggregate_failures: false do |example| 7 | expect(example.metadata).to include(aggregate_failures: false) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /13-understanding-test-doubles/exercises/mountain/lib/skier.rb: -------------------------------------------------------------------------------- 1 | module Mountain 2 | class Skier 3 | def initialize(trail_map) 4 | @trail_map = trail_map 5 | end 6 | 7 | def ski_on(trail_name) 8 | difficulty = @trail_map.difficulty(trail_name) 9 | @tired = true if difficulty == :expert 10 | end 11 | 12 | def tired? 13 | @tired 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /A2-using-rspec-with-rails/01/sessions/install_rspec.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rails generate rspec:install 5 |  create .rspec 6 |  create spec 7 |  create spec/spec_helper.rb 8 |  create spec/rails_helper.rb 9 | 10 | -------------------------------------------------------------------------------- /07-structuring-code-examples/02/expense_tracker/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source "https://rubygems.org" 3 | 4 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 5 | 6 | gem 'rspec', '3.6.0' 7 | gem 'coderay', '1.1.1' 8 | gem 'rack-test', '0.7.0' 9 | gem 'sinatra', '2.0.0' 10 | gem 'sequel', '4.48.0' 11 | gem 'sqlite3', '1.3.13' 12 | 13 | gem 'pry', '0.10.4' 14 | -------------------------------------------------------------------------------- /13-understanding-test-doubles/exercises/mountain/spec/skier_spec.rb: -------------------------------------------------------------------------------- 1 | require 'skier' 2 | 3 | module Mountain 4 | RSpec.describe Skier do 5 | it 'gets tired after skiing a difficult slope' do 6 | trail_map = instance_double('TrailMap', difficulty: :expert) 7 | 8 | skier = Skier.new(trail_map) 9 | skier.ski_on('Last Hoot') 10 | expect(skier).to be_tired 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /02-running-specs/06/sessions/rspec_runs/6.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec --only-failures 5 | Run options: include {:last_run_status=>"failed"} 6 | 7 | All examples were filtered out 8 | 9 | 10 | Finished in 0.00031 seconds (files took 0.08117 seconds to load) 11 | 0 examples, 0 failures 12 | 13 | -------------------------------------------------------------------------------- /13-understanding-test-doubles/02/expense_tracker/spec/unit/ledger_double_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Using a ledger double' do 2 | example 'a pure double' do 3 | ledger = double('ExpenseTracker::Ledger') 4 | allow(ledger).to receive(:record) 5 | end 6 | 7 | example 'a verifying double' do 8 | ledger = instance_double('ExpenseTracker::Ledger') 9 | allow(ledger).to receive(:record) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /05-unit-specs/10/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec spec/unit/app/api_spec.rb 5 | truncated 6 | 7 | Finished in 0.0266 seconds (files took 0.13459 seconds to load) 8 | 4 examples, 0 failures 9 | 10 | Randomized with seed 13686 11 | 12 | -------------------------------------------------------------------------------- /02-running-specs/exercises/spec/tea_spec.rb: -------------------------------------------------------------------------------- 1 | class Tea 2 | end 3 | 4 | RSpec.configure do |config| 5 | config.example_status_persistence_file_path = 'spec/examples.txt' 6 | end 7 | 8 | RSpec.describe Tea do 9 | let(:tea) { Tea.new } 10 | 11 | it 'tastes like Earl Grey' do 12 | expect(tea.flavor).to be :earl_grey 13 | end 14 | 15 | it 'is hot' do 16 | expect(tea.temperature).to be > 200.0 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /05-unit-specs/03/sessions/rspec_runs/2.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec spec/unit/app/api_spec.rb 5 | truncated 6 | 7 | Finished in 0.02645 seconds (files took 0.14491 seconds to load) 8 | 4 examples, 0 failures, 3 pending 9 | 10 | Randomized with seed 23924 11 | 12 | -------------------------------------------------------------------------------- /11-matchers-included-in-rspec-expectations/solutions/calendar_spec.rb: -------------------------------------------------------------------------------- 1 | require 'date' 2 | 3 | Calendar = Struct.new(:date_string) do 4 | def on_weekend? 5 | Date.parse(date_string).saturday? 6 | end 7 | end 8 | 9 | RSpec.describe Calendar do 10 | let(:sunday_date) { Calendar.new('Sun, 11 Jun 2017') } 11 | 12 | it 'considers sundays to be on the weekend' do 13 | expect(sunday_date).to be_on_weekend 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /02-running-specs/05/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec 5 | .F 6 | 7 | truncated 8 | 9 | 2 examples, 1 failure 10 | 11 | Failed examples: 12 | 13 | rspec ./spec/coffee_spec.rb:25 # A cup of coffee with milk costs $1.25 14 | 15 | -------------------------------------------------------------------------------- /02-running-specs/06/sessions/rspec_runs/2.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec 5 | .F 6 | 7 | truncated 8 | 9 | 2 examples, 1 failure 10 | 11 | Failed examples: 12 | 13 | rspec ./spec/coffee_spec.rb:29 # A cup of coffee with milk costs $1.25 14 | 15 | -------------------------------------------------------------------------------- /06-integration-specs/08/sessions/rspec_runs/2.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec spec/integration/app/ledger_spec.rb 5 | truncated 6 | 7 | Finished in 0.01205 seconds (files took 0.15597 seconds to load) 8 | 2 examples, 0 failures 9 | 10 | Randomized with seed 38450 11 | 12 | -------------------------------------------------------------------------------- /11-matchers-included-in-rspec-expectations/exercises/calendar_spec.rb: -------------------------------------------------------------------------------- 1 | require 'date' 2 | 3 | Calendar = Struct.new(:date_string) do 4 | def on_weekend? 5 | Date.parse(date_string).saturday? 6 | end 7 | end 8 | 9 | RSpec.describe Calendar do 10 | let(:sunday_date) { Calendar.new('Sun, 11 Jun 2017') } 11 | 12 | it 'considers sundays to be on the weekend' do 13 | expect(sunday_date.on_weekend?).to be true 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/16/api_request_tracker/lib/api_request_tracker.rb: -------------------------------------------------------------------------------- 1 | require 'metrics_reporter' 2 | require 'endpoint' 3 | 4 | class APIRequestTracker 5 | def initialize(reporter: MetricsReporter.new) 6 | @reporter = reporter 7 | end 8 | 9 | def process(request) 10 | endpoint_description = Endpoint.description_of(request) 11 | @reporter.increment("api.requests.#{endpoint_description}") 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/08/subscription_service/lib/recurring_payment.rb: -------------------------------------------------------------------------------- 1 | require 'cash_cow' 2 | 3 | class RecurringPayment 4 | def self.process_subscriptions(subscriptions) 5 | cards_and_amounts = subscriptions.each_with_object({}) do |sub, data| 6 | data[sub.credit_card] = sub.amount 7 | end 8 | 9 | CashCow.bulk_charge_cards(cards_and_amounts) 10 | # ...send receipts and other stuff... 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/12/api_request_tracker/spec/api_request_tracker_spec.rb: -------------------------------------------------------------------------------- 1 | require 'api_request_tracker' 2 | 3 | RSpec.describe APIRequestTracker do 4 | let(:request) { Request.new(:get, '/users') } 5 | 6 | it 'increments the request counter' do 7 | expect(MetricsReporter).to receive(:increment).with( 8 | 'api.requests.get_users' 9 | ) 10 | 11 | APIRequestTracker.new.process(request) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/21/sales_tax/lib/invoice.rb: -------------------------------------------------------------------------------- 1 | require 'my_app' 2 | 3 | class Invoice 4 | def initialize(address, items, tax_client: MyApp.tax_client) 5 | @address = address 6 | @items = items 7 | @tax_client = tax_client 8 | end 9 | 10 | def calculate_total 11 | subtotal = @items.map(&:cost).inject(0, :+) 12 | taxes = subtotal * tax_rate 13 | subtotal + taxes 14 | end 15 | 16 | # ... 17 | end 18 | -------------------------------------------------------------------------------- /07-structuring-code-examples/06/transit/lib/berlin_transit_ticket.rb: -------------------------------------------------------------------------------- 1 | class BerlinTransitTicket 2 | attr_accessor :starting_station, :ending_station 3 | 4 | def fare 5 | if starting_station == 'Bundestag' && ending_station == 'Leopoldplatz' 6 | 2.7 7 | elsif starting_station == 'Bundestag' && ending_station == 'Birkenwerder' 8 | 3.3 9 | else 10 | raise 'price has not been defined' 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /10-exploring-rspec-expectations/06/spec/cookie_recipe_should_spec.rb: -------------------------------------------------------------------------------- 1 | class CookieRecipe 2 | attr_reader :ingredients 3 | 4 | def initialize 5 | @ingredients = [:butter, :milk, :flour, :sugar, :eggs, :chocolate_chips] 6 | end 7 | end 8 | 9 | RSpec.describe CookieRecipe, '#ingredients' do 10 | subject { CookieRecipe.new.ingredients } 11 | it { should include(:butter, :milk, :eggs) } 12 | it { should_not include(:fish_oil) } 13 | end 14 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/17/api_request_tracker/lib/api_request_tracker.rb: -------------------------------------------------------------------------------- 1 | require 'metrics_reporter' 2 | require 'endpoint' 3 | 4 | class APIRequestTracker 5 | attr_writer :reporter 6 | def reporter 7 | @reporter ||= MetricsReporter.new 8 | end 9 | 10 | def process(request) 11 | endpoint_description = Endpoint.description_of(request) 12 | reporter.increment("api.requests.#{endpoint_description}") 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /A1-rspec-and-wider-ecosystem/01/sessions/each_spec_bin_rspec.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ time (for file in spec/**/*_spec.rb ↩ 5 | do bin/rspec $file || exit 1 ↩ 6 | done) > /dev/null 7 | 8 | 0.85s user 0.11s system 97% cpu 0.983 total 9 | 10 | -------------------------------------------------------------------------------- /A1-rspec-and-wider-ecosystem/01/sessions/each_spec_bundle_exec.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ time (for file in spec/**/*_spec.rb ↩ 5 | do bundle exec rspec $file || exit 1 ↩ 6 | done) > /dev/null 7 | 8 | 1.50s user 0.17s system 98% cpu 1.707 total 9 | 10 | -------------------------------------------------------------------------------- /10-exploring-rspec-expectations/05/generated_example_descriptions.irbr: -------------------------------------------------------------------------------- 1 | >> require 'rspec/expectations' 2 | => true 3 | >> include RSpec::Matchers 4 | => Object 5 | 6 | >> start_with(1).description 7 | => "start with 1" 8 | >> (start_with(1) & end_with(9)).description 9 | => "start with 1 and end with 9" 10 | >> contain_exactly( a_string_starting_with(1) & ending_with(9) ).description 11 | => "contain exactly (a string starting with 1 and ending with 9)" 12 | -------------------------------------------------------------------------------- /14-customizing-test-doubles/01/and_return.irbr: -------------------------------------------------------------------------------- 1 | >> require 'rspec/mocks/standalone' 2 | => true 3 | >> random = double('Random') 4 | => # 5 | 6 | >> allow(random).to receive(:rand).and_return(0.1, 0.2, 0.3) 7 | => #.rand(any arguments)> 8 | >> random.rand 9 | => 0.1 10 | >> random.rand 11 | => 0.2 12 | >> random.rand 13 | => 0.3 14 | >> random.rand 15 | => 0.3 16 | >> random.rand 17 | => 0.3 18 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/01/password_strength_validator/lib/password_strength_validator.rb: -------------------------------------------------------------------------------- 1 | require 'configuration' 2 | 3 | class PasswordStrengthValidator 4 | 5 | attr_reader :password 6 | 7 | def initialize(password) 8 | @password = password 9 | end 10 | 11 | def strong_enough? 12 | return false unless password.length >= Acme::Config.min_password_length 13 | 14 | # ... more validations ... 15 | true 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/10/api_request_tracker/spec/api_request_tracker_spec.rb: -------------------------------------------------------------------------------- 1 | require 'api_request_tracker' 2 | 3 | RSpec.describe APIRequestTracker do 4 | let(:request) { Request.new(:get, '/users') } 5 | 6 | it 'increments the request counter' do 7 | expect_any_instance_of(MetricsReporter).to receive(:increment).with( 8 | 'api.requests.get_users' 9 | ) 10 | 11 | APIRequestTracker.new.process(request) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/22/sales_tax/spec/integration/invoice_spec.rb: -------------------------------------------------------------------------------- 1 | require 'invoice' 2 | 3 | RSpec.describe Invoice do 4 | let(:address) { Address.new(zip: '98122') } # Seattle zip 5 | let(:items) { [Item.new(cost: 30), Item.new(cost: 70)] } 6 | 7 | it 'calculates the total' do 8 | invoice = Invoice.new(address, items) 9 | 10 | # Seattle's tax rate is 10.1% 11 | expect(invoice.calculate_total).to eq(110.10) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /10-exploring-rspec-expectations/06/spec/cookie_recipe_subject_spec.rb: -------------------------------------------------------------------------------- 1 | class CookieRecipe 2 | attr_reader :ingredients 3 | 4 | def initialize 5 | @ingredients = [:butter, :milk, :flour, :sugar, :eggs, :chocolate_chips] 6 | end 7 | end 8 | 9 | RSpec.describe CookieRecipe, '#ingredients' do 10 | subject { CookieRecipe.new.ingredients } 11 | it { is_expected.to include(:butter, :milk, :eggs) } 12 | it { is_expected.not_to include(:fish_oil) } 13 | end 14 | -------------------------------------------------------------------------------- /06-integration-specs/03/sessions/sequel_migration.out.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec sequel -m ./db/migrations sqlite://db/development.db --echo 5 | truncated 6 | 7 | I, [2017-06-13T13:34:25.536511 #14630] INFO -- : Finished applying ↩ 8 | migration version 1, direction: up, took 0.001514 seconds 9 | 10 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/07/custom_matcher/spec/support/matchers.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :have_a_balance_of do |amount| 2 | match { |account| account.current_balance == amount } 3 | failure_message { |account| super() + failure_reason(account) } 4 | failure_message_when_negated { |account| super() + failure_reason(account) } 5 | 6 | private 7 | 8 | def failure_reason(account) 9 | ", but had a balance of #{account.current_balance}" 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /02-running-specs/solutions/01/spec/tea_spec.rb: -------------------------------------------------------------------------------- 1 | class Tea 2 | def flavor 3 | :earl_grey 4 | end 5 | end 6 | 7 | RSpec.configure do |config| 8 | config.example_status_persistence_file_path = 'spec/examples.txt' 9 | end 10 | 11 | RSpec.describe Tea do 12 | let(:tea) { Tea.new } 13 | 14 | it 'tastes like Earl Grey' do 15 | expect(tea.flavor).to be :earl_grey 16 | end 17 | 18 | it 'is hot' do 19 | expect(tea.temperature).to be > 200.0 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /07-structuring-code-examples/04/around_hooks_spec.rb: -------------------------------------------------------------------------------- 1 | module MyApp 2 | class Configuration 3 | end 4 | end 5 | 6 | RSpec.describe MyApp::Configuration do 7 | around(:example) do |ex| 8 | original_env = ENV.to_hash 9 | ex.run 10 | ENV.replace(original_env) 11 | end 12 | 13 | it 'can mutate env' do 14 | ENV['FOO'] = '1' 15 | end 16 | 17 | it 'isolates mutations from other examples' do 18 | expect(ENV['FOO']).to eq nil 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/14/api_request_tracker/spec/api_request_tracker_spec.rb: -------------------------------------------------------------------------------- 1 | require 'api_request_tracker' 2 | 3 | RSpec.describe APIRequestTracker do 4 | let(:request) { Request.new(:get, '/users') } 5 | 6 | it 'increments the request counter' do 7 | reporter = class_double(MetricsReporter).as_stubbed_const 8 | expect(reporter).to receive(:increment).with('api.requests.get_users') 9 | 10 | APIRequestTracker.new.process(request) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/15/api_request_tracker/spec/api_request_tracker_spec.rb: -------------------------------------------------------------------------------- 1 | require 'api_request_tracker' 2 | 3 | RSpec.describe APIRequestTracker do 4 | let(:request) { Request.new(:get, '/users') } 5 | 6 | it 'increments the request counter' do 7 | reporter = class_double(MetricsReporter) 8 | expect(reporter).to receive(:increment).with('api.requests.get_users') 9 | 10 | APIRequestTracker.new.process(request, reporter: reporter) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/16/api_request_tracker/spec/api_request_tracker_spec.rb: -------------------------------------------------------------------------------- 1 | require 'api_request_tracker' 2 | 3 | RSpec.describe APIRequestTracker do 4 | let(:request) { Request.new(:get, '/users') } 5 | 6 | it 'increments the request counter' do 7 | reporter = class_double(MetricsReporter) 8 | expect(reporter).to receive(:increment).with('api.requests.get_users') 9 | 10 | APIRequestTracker.new(reporter: reporter).process(request) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/23/sales_tax/lib/sales_tax.rb: -------------------------------------------------------------------------------- 1 | require 'my_app' 2 | 3 | class SalesTax 4 | RateUnavailableError = Class.new(StandardError) 5 | 6 | def initialize(tax_client = MyApp.tax_client) 7 | @tax_client = tax_client 8 | end 9 | 10 | def rate_for(zip) 11 | @tax_client.rates_for_location(zip).combined_rate 12 | rescue Taxjar::Error::NotFound 13 | raise RateUnavailableError, "Sales tax rate unavailable for zip: #{zip}" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /01-getting-started/09/spec/sandwich_spec.rb: -------------------------------------------------------------------------------- 1 | Sandwich = Struct.new(:taste, :toppings) 2 | 3 | RSpec.describe 'An ideal sandwich' do 4 | let(:sandwich) { Sandwich.new('delicious', []) } 5 | 6 | it 'is delicious' do 7 | taste = sandwich.taste 8 | 9 | expect(taste).to eq('delicious') 10 | end 11 | 12 | it 'lets me add toppings' do 13 | sandwich.toppings << 'cheese' 14 | toppings = sandwich.toppings 15 | 16 | expect(toppings).not_to be_empty 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/21/sales_tax/lib/my_app.rb: -------------------------------------------------------------------------------- 1 | require 'taxjar' 2 | require 'dotenv' 3 | Dotenv.load('api_creds.env') 4 | 5 | Address = Struct.new(:zip) do 6 | def initialize(zip:) 7 | super(zip) 8 | end 9 | end 10 | 11 | Item = Struct.new(:cost) do 12 | def initialize(cost:) 13 | super(cost) 14 | end 15 | end 16 | 17 | module MyApp 18 | def self.tax_client 19 | @tax_client ||= Taxjar::Client.new(api_key: ENV['TAXJAR_API_KEY']) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /06-integration-specs/09/sessions/curl_failure.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ curl localhost:9292/expenses/2017-06-10 -w "\n" 5 | NameError: uninitialized constant ExpenseTracker::Ledger::DB 6 | ~/code/expense_tracker/app/ledger.rb:17:in `expenses_on' 7 | ~/code/expense_tracker/app/api.rb:25:in `block in ' 8 | 9 | truncated 10 | 11 | -------------------------------------------------------------------------------- /07-structuring-code-examples/solutions/shared_examples_exercise/spec/uri_spec.rb: -------------------------------------------------------------------------------- 1 | require 'uri' 2 | require 'support/shared_examples' 3 | 4 | RSpec.describe URI do 5 | it_behaves_like 'a URI parsing library', URI 6 | 7 | it 'defaults the port for an http URI to 80' do 8 | expect(URI.parse('http://example.com/').port).to eq 80 9 | end 10 | 11 | it 'defaults the port for an https URI to 443' do 12 | expect(URI.parse('https://example.com/').port).to eq 443 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /09-configuring-rspec/02/configuring_rspec/a_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'A group with a failure' do 2 | it 'has an example that fails' do 3 | expect(1).to eq 2 4 | end 5 | 6 | it 'has an example that succeeds' do 7 | expect(1).to eq 1 8 | end 9 | end 10 | 11 | RSpec.describe 'Another group with a failure' do 12 | it 'has an example that fails' do 13 | expect(1).to eq 2 14 | end 15 | 16 | it 'has an example that succeeds' do 17 | expect(1).to eq 1 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/24/sales_tax/spec/unit/invoice_spec.rb: -------------------------------------------------------------------------------- 1 | require 'invoice' 2 | 3 | RSpec.describe Invoice do 4 | let(:address) { Address.new(zip: '90210') } 5 | let(:items) { [Item.new(cost: 30), Item.new(cost: 70)] } 6 | 7 | it 'calculates the total' do 8 | sales_tax = instance_double(SalesTax, rate_for: 0.095) 9 | 10 | invoice = Invoice.new(address, items, sales_tax: sales_tax) 11 | 12 | expect(invoice.calculate_total).to eq(109.50) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /01-getting-started/05/spec/sandwich_spec.rb: -------------------------------------------------------------------------------- 1 | Sandwich = Struct.new(:taste, :toppings) 2 | 3 | RSpec.describe 'An ideal sandwich' do 4 | before { @sandwich = Sandwich.new('delicious', []) } 5 | 6 | it 'is delicious' do 7 | taste = @sandwich.taste 8 | 9 | expect(taste).to eq('delicious') 10 | end 11 | 12 | it 'lets me add toppings' do 13 | @sandwich.toppings << 'cheese' 14 | toppings = @sandwich.toppings 15 | 16 | expect(toppings).not_to be_empty 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /02-running-specs/06/sessions/rspec_runs/3.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec --only-failures 5 | Run options: include {:last_run_status=>"failed"} 6 | F 7 | 8 | truncated 9 | 10 | 1 example, 1 failure 11 | 12 | Failed examples: 13 | 14 | rspec ./spec/coffee_spec.rb:29 # A cup of coffee with milk costs $1.25 15 | 16 | -------------------------------------------------------------------------------- /01-getting-started/06/spec/sandwich_spec.rb: -------------------------------------------------------------------------------- 1 | Sandwich = Struct.new(:taste, :toppings) 2 | 3 | RSpec.describe 'An ideal sandwich' do 4 | def sandwich 5 | Sandwich.new('delicious', []) 6 | end 7 | 8 | it 'is delicious' do 9 | taste = sandwich.taste 10 | 11 | expect(taste).to eq('delicious') 12 | end 13 | 14 | it 'lets me add toppings' do 15 | sandwich.toppings << 'cheese' 16 | toppings = sandwich.toppings 17 | 18 | expect(toppings).not_to be_empty 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /10-exploring-rspec-expectations/06/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec spec/cookie_recipe_spec.rb --format documentation 5 | 6 | CookieRecipe#ingredients 7 |  should include :butter, :milk and :eggs 8 |  should not include :fish_oil 9 | 10 | Finished in 0.00197 seconds (files took 0.08433 seconds to load) 11 | 2 examples, 0 failures 12 | 13 | -------------------------------------------------------------------------------- /01-getting-started/01/sessions/install_rspec.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ gem install rspec -v 3.6.0 5 | Successfully installed rspec-support-3.6.0 6 | Successfully installed rspec-core-3.6.0 7 | Successfully installed diff-lcs-1.3 8 | Successfully installed rspec-expectations-3.6.0 9 | Successfully installed rspec-mocks-3.6.0 10 | Successfully installed rspec-3.6.0 11 | 6 gems installed 12 | 13 | -------------------------------------------------------------------------------- /04-acceptance-specs/01/expense_tracker/spec/acceptance/expense_tracker_api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rack/test' 2 | require 'json' 3 | 4 | module ExpenseTracker 5 | RSpec.describe 'Expense Tracker API' do 6 | include Rack::Test::Methods 7 | 8 | it 'records submitted expenses' do 9 | coffee = { 10 | 'payee' => 'Starbucks', 11 | 'amount' => 5.75, 12 | 'date' => '2017-06-10' 13 | } 14 | 15 | post '/expenses', JSON.generate(coffee) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /02-running-specs/06/sessions/rspec_runs/4.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec --tag last_run_status:failed 5 | Run options: include {:last_run_status=>"failed"} 6 | F 7 | 8 | truncated 9 | 10 | 1 example, 1 failure 11 | 12 | Failed examples: 13 | 14 | rspec ./spec/coffee_spec.rb:29 # A cup of coffee with milk costs $1.25 15 | 16 | -------------------------------------------------------------------------------- /07-structuring-code-examples/04/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.around(:example) do |ex| 3 | original_env = ENV.to_hash 4 | ex.run 5 | ENV.replace(original_env) 6 | end 7 | end 8 | 9 | require 'fileutils' 10 | 11 | RSpec.configure do |config| 12 | config.before(:suite) do 13 | # Remove leftover temporary files 14 | FileUtils.rm_rf('tmp') 15 | end 16 | end 17 | 18 | RSpec.configure do |config| 19 | config.before(:all) do 20 | # ... 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /07-structuring-code-examples/11/sessions/rspec_runs/2.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec spec/include_examples_spec.rb --format documentation 5 | 6 | HashKVStore 7 |  allows you to fetch previously stored values 8 |  raises a KeyError when you fetch an unknown key 9 | 10 | Finished in 0.00309 seconds (files took 0.09898 seconds to load) 11 | 2 examples, 0 failures 12 | 13 | -------------------------------------------------------------------------------- /10-exploring-rspec-expectations/06/sessions/rspec_runs/3.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec spec/cookie_recipe_subject_spec.rb --format documentation 5 | 6 | CookieRecipe#ingredients 7 |  should include :butter, :milk, and :eggs 8 |  should not include :fish_oil 9 | 10 | Finished in 0.00225 seconds (files took 0.08616 seconds to load) 11 | 2 examples, 0 failures 12 | 13 | -------------------------------------------------------------------------------- /10-exploring-rspec-expectations/06/sessions/rspec_runs/4.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec spec/cookie_recipe_should_spec.rb --format documentation 5 | 6 | CookieRecipe#ingredients 7 |  should include :butter, :milk, and :eggs 8 |  should not include :fish_oil 9 | 10 | Finished in 0.00147 seconds (files took 0.08302 seconds to load) 11 | 2 examples, 0 failures 12 | 13 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/13/api_request_tracker/spec/api_request_tracker_spec.rb: -------------------------------------------------------------------------------- 1 | require 'api_request_tracker' 2 | 3 | RSpec.describe APIRequestTracker do 4 | let(:request) { Request.new(:get, '/users') } 5 | 6 | it 'increments the request counter' do 7 | reporter = class_double(MetricsReporter) 8 | stub_const('MetricsReporter', reporter) 9 | expect(reporter).to receive(:increment).with('api.requests.get_users') 10 | 11 | APIRequestTracker.new.process(request) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /01-getting-started/07/spec/sandwich_spec.rb: -------------------------------------------------------------------------------- 1 | Sandwich = Struct.new(:taste, :toppings) 2 | 3 | RSpec.describe 'An ideal sandwich' do 4 | def sandwich 5 | @sandwich ||= Sandwich.new('delicious', []) 6 | end 7 | 8 | it 'is delicious' do 9 | taste = sandwich.taste 10 | 11 | expect(taste).to eq('delicious') 12 | end 13 | 14 | it 'lets me add toppings' do 15 | sandwich.toppings << 'cheese' 16 | toppings = sandwich.toppings 17 | 18 | expect(toppings).not_to be_empty 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /10-exploring-rspec-expectations/06/sessions/rspec_runs/2.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec spec/cookie_recipe_no_doc_strings_spec.rb --format documentation 5 | 6 | CookieRecipe#ingredients 7 |  should include :butter, :milk, and :eggs 8 |  should not include :fish_oil 9 | 10 | Finished in 0.00212 seconds (files took 0.08655 seconds to load) 11 | 2 examples, 0 failures 12 | 13 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/06/sessions/minimal_matcher_failure.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1) `have_a_balance_of(amount)` fails when the balance does not match 5 | Failure/Error: expect(account).to have_a_balance_of(35) 6 |  expected # to have a balance of 35 7 | # ./spec/initial_account_spec.rb:17:in `block (2 levels) in 9 | -------------------------------------------------------------------------------- /02-running-specs/05/sessions/rspec_runs/2.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec ./spec/coffee_spec.rb:25 5 | Run options: include {:locations=>{"./spec/coffee_spec.rb"=>[25]}} 6 | F 7 | 8 | truncated 9 | 10 | 1 example, 1 failure 11 | 12 | Failed examples: 13 | 14 | rspec ./spec/coffee_spec.rb:25 # A cup of coffee with milk costs $1.25 15 | 16 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/18/twitter_user_formatter/spec/twitter_user_formatter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'twitter_user_formatter' 2 | 3 | RSpec.describe TwitterUserFormatter do 4 | it 'describes their homepage' do 5 | user = instance_double(Twitter::User, 6 | name: 'RSpec', 7 | url: 'http://rspec.info') 8 | 9 | formatter = TwitterUserFormatter.new(user) 10 | 11 | expect(formatter.format).to eq("RSpec's website is http://rspec.info") 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /10-exploring-rspec-expectations/06/spec/cookie_recipe_no_doc_strings_spec.rb: -------------------------------------------------------------------------------- 1 | class CookieRecipe 2 | attr_reader :ingredients 3 | 4 | def initialize 5 | @ingredients = [:butter, :milk, :flour, :sugar, :eggs, :chocolate_chips] 6 | end 7 | end 8 | 9 | RSpec.describe CookieRecipe, '#ingredients' do 10 | specify do 11 | expect(CookieRecipe.new.ingredients).to include(:butter, :milk, :eggs) 12 | end 13 | 14 | specify do 15 | expect(CookieRecipe.new.ingredients).not_to include(:fish_oil) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/17/api_request_tracker/spec/api_request_tracker_spec.rb: -------------------------------------------------------------------------------- 1 | require 'api_request_tracker' 2 | 3 | RSpec.describe APIRequestTracker do 4 | let(:request) { Request.new(:get, '/users') } 5 | 6 | it 'increments the request counter' do 7 | reporter = class_double(MetricsReporter) 8 | expect(reporter).to receive(:increment).with('api.requests.get_users') 9 | 10 | tracker = APIRequestTracker.new 11 | tracker.reporter = reporter 12 | tracker.process(request) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /A2-using-rspec-with-rails/01/sessions/generate_pterodactyl.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rails generate model pterodactyl 5 |  invoke active_record 6 |  create db/migrate/20170613203526_create_pterodactyls.rb 7 |  create app/models/pterodactyl.rb 8 |  invoke rspec 9 |  create spec/models/pterodactyl_spec.rb 10 | 11 | -------------------------------------------------------------------------------- /02-running-specs/solutions/02/spec/tea_spec.rb: -------------------------------------------------------------------------------- 1 | class Tea 2 | def flavor 3 | :earl_grey 4 | end 5 | 6 | def temperature 7 | 205 8 | end 9 | end 10 | 11 | RSpec.configure do |config| 12 | config.example_status_persistence_file_path = 'spec/examples.txt' 13 | end 14 | 15 | RSpec.describe Tea do 16 | let(:tea) { Tea.new } 17 | 18 | it 'tastes like Earl Grey' do 19 | expect(tea.flavor).to be :earl_grey 20 | end 21 | 22 | it 'is hot' do 23 | expect(tea.temperature).to be > 200.0 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /06-integration-specs/09/sessions/rspec_runs/4.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec 5 | 6 | Randomized with seed 14629 7 | ............. 8 | 9 | Finished in 0.04986 seconds (files took 0.21623 seconds to load) 10 | 13 examples, 0 failures 11 | 12 | Randomized with seed 14629 13 | 14 | -------------------------------------------------------------------------------- /01-getting-started/04/spec/sandwich_spec.rb: -------------------------------------------------------------------------------- 1 | Sandwich = Struct.new(:taste, :toppings) 2 | 3 | RSpec.describe 'An ideal sandwich' do 4 | it 'is delicious' do 5 | sandwich = Sandwich.new('delicious', []) 6 | 7 | taste = sandwich.taste 8 | 9 | expect(taste).to eq('delicious') 10 | end 11 | 12 | it 'lets me add toppings' do 13 | sandwich = Sandwich.new('delicious', []) 14 | 15 | sandwich.toppings << 'cheese' 16 | toppings = sandwich.toppings 17 | 18 | expect(toppings).not_to be_empty 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /11-matchers-included-in-rspec-expectations/exercises/tokenizer_spec.rb: -------------------------------------------------------------------------------- 1 | class Tokenizer 2 | def self.tokenize(string) 3 | string.split(/ +/) 4 | end 5 | end 6 | 7 | RSpec.describe Tokenizer do 8 | let(:text) do 9 | <<-EOS 10 | I am Sam. 11 | Sam I am. 12 | Do you like green eggs and ham? 13 | EOS 14 | end 15 | 16 | it 'tokenizes multiple lines of text' do 17 | tokenized = Tokenizer.tokenize(text) 18 | expect(tokenized.first(6)).to eq ['I', 'am', 'Sam.', 'Sam', 'I', 'am'] 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /11-matchers-included-in-rspec-expectations/solutions/tokenizer_spec.rb: -------------------------------------------------------------------------------- 1 | class Tokenizer 2 | def self.tokenize(string) 3 | string.split(/ +/) 4 | end 5 | end 6 | 7 | RSpec.describe Tokenizer do 8 | let(:text) do 9 | <<-EOS 10 | I am Sam. 11 | Sam I am. 12 | Do you like green eggs and ham? 13 | EOS 14 | end 15 | 16 | it 'tokenizes multiple lines of text' do 17 | tokenized = Tokenizer.tokenize(text) 18 | expect(tokenized).to start_with('I', 'am', 'Sam.', 'Sam', 'I', 'am') 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/02/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec 5 | 6 | Randomized with seed 21580 7 | ............. 8 | 9 | Finished in 0.05467 seconds (files took 0.22211 seconds to load) 10 | 13 examples, 0 failures 11 | 12 | Randomized with seed 21580 13 | 14 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/03/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec 5 | 6 | Randomized with seed 39552 7 | ............. 8 | 9 | Finished in 0.04785 seconds (files took 0.2185 seconds to load) 10 | 13 examples, 0 failures 11 | 12 | Randomized with seed 39552 13 | 14 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/04/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec 5 | 6 | Randomized with seed 21225 7 | ............. 8 | 9 | Finished in 0.05466 seconds (files took 0.20993 seconds to load) 10 | 13 examples, 0 failures 11 | 12 | Randomized with seed 21225 13 | 14 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/11/api_request_tracker/spec/api_request_tracker_spec.rb: -------------------------------------------------------------------------------- 1 | require 'api_request_tracker' 2 | 3 | RSpec.describe APIRequestTracker do 4 | let(:request) { Request.new(:get, '/users') } 5 | 6 | it 'increments the request counter' do 7 | reporter = instance_double(MetricsReporter) 8 | allow(MetricsReporter).to receive(:new).and_return(reporter) 9 | expect(reporter).to receive(:increment).with('api.requests.get_users') 10 | 11 | APIRequestTracker.new.process(request) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /07-structuring-code-examples/11/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec spec/hash_kv_store_spec.rb --format documentation 5 | 6 | HashKVStore 7 | behaves like KV store 8 |  allows you to fetch previously stored values 9 |  raises a KeyError when you fetch an unknown key 10 | 11 | Finished in 0.0034 seconds (files took 0.09656 seconds to load) 12 | 2 examples, 0 failures 13 | 14 | -------------------------------------------------------------------------------- /07-structuring-code-examples/11/sessions/rspec_runs/5.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec spec/pass_block_spec.rb --format documentation 5 | 6 | FileKVStore 7 | behaves like KV store 8 |  allows you to fetch previously stored values 9 |  raises a KeyError when you fetch an unknown key 10 | 11 | Finished in 0.00279 seconds (files took 0.10623 seconds to load) 12 | 2 examples, 0 failures 13 | 14 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/24/sales_tax/lib/invoice.rb: -------------------------------------------------------------------------------- 1 | require 'my_app' 2 | require 'sales_tax' 3 | 4 | class Invoice 5 | def initialize(address, items, sales_tax: SalesTax.new) 6 | @address = address 7 | @items = items 8 | @sales_tax = sales_tax 9 | end 10 | 11 | def calculate_total 12 | subtotal = @items.map(&:cost).inject(0, :+) 13 | taxes = subtotal * tax_rate 14 | subtotal + taxes 15 | end 16 | 17 | private 18 | 19 | def tax_rate 20 | @sales_tax.rate_for(@address.zip) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /05-unit-specs/03/expense_tracker/app/api.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require 'json' 3 | 4 | module ExpenseTracker 5 | class API < Sinatra::Base 6 | def initialize(ledger: Ledger.new) 7 | @ledger = ledger 8 | super() 9 | end 10 | 11 | post '/expenses' do 12 | expense = JSON.parse(request.body.read) 13 | result = @ledger.record(expense) 14 | JSON.generate('expense_id' => result.expense_id) 15 | end 16 | 17 | get '/expenses/:date' do 18 | JSON.generate([]) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/22/sales_tax/lib/invoice.rb: -------------------------------------------------------------------------------- 1 | require 'my_app' 2 | 3 | class Invoice 4 | def initialize(address, items, tax_client: MyApp.tax_client) 5 | @address = address 6 | @items = items 7 | @tax_client = tax_client 8 | end 9 | 10 | def calculate_total 11 | subtotal = @items.map(&:cost).inject(0, :+) 12 | taxes = subtotal * tax_rate 13 | subtotal + taxes 14 | end 15 | 16 | private 17 | 18 | def tax_rate 19 | @tax_client.rates_for_location(@address.zip).combined_rate 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/24/sales_tax/spec/integration/sales_tax_spec.rb: -------------------------------------------------------------------------------- 1 | require 'sales_tax' 2 | 3 | RSpec.describe SalesTax do 4 | let(:sales_tax) { SalesTax.new } 5 | 6 | it 'can fetch the tax rate for a given zip' do 7 | rate = sales_tax.rate_for('90210') 8 | expect(rate).to be_a(Float).and be_between(0.01, 0.5) 9 | end 10 | 11 | it 'raises an error if the tax rate cannot be found' do 12 | expect { 13 | sales_tax.rate_for('00000') 14 | }.to raise_error(SalesTax::RateUnavailableError) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /04-acceptance-specs/04/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec 5 | F 6 | 7 | Failures: 8 | 9 | 1) Expense Tracker API records submitted expenses 10 | Failure/Error: parsed = JSON.parse(last_response.body) 11 |  12 | JSON::ParserError: 13 |  743: unexpected token at '' 14 | 15 | truncated 16 | 17 | -------------------------------------------------------------------------------- /07-structuring-code-examples/11/shared_examples/lib/file_kv_store.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | require 'pstore' 3 | 4 | class FileKVStore 5 | def initialize(filename = Tempfile.new('pstore').path) 6 | @pstore = PStore.new(filename) 7 | end 8 | 9 | def store(key, value) 10 | @pstore.transaction do 11 | @pstore[key] = value 12 | end 13 | end 14 | 15 | def fetch(key) 16 | @pstore.transaction do 17 | @pstore.fetch(key) 18 | end 19 | rescue PStore::Error => e 20 | raise KeyError, e.message 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /08-metadata/07/aggregate_failures.rb: -------------------------------------------------------------------------------- 1 | # validate me 2 | require 'rspec' 3 | 4 | RSpec.configure do |config| 5 | config.define_derived_metadata do |meta| 6 | # Sets the flag unconditionally; 7 | # doesn't allow examples to opt out 8 | meta[:aggregate_failures] = true 9 | end 10 | end 11 | 12 | RSpec.describe 'Billing', aggregate_failures: false do 13 | context 'using the fake payment service' do 14 | before do 15 | expect(MyApp.config.payment_gateway).to include('sandbox') 16 | end 17 | 18 | # ... 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/19/ruby_doc_server/spec/ruby_doc_server_spec.rb: -------------------------------------------------------------------------------- 1 | require 'ruby_doc_server' 2 | 3 | RSpec.describe RubyDocServer do 4 | it 'finds matching ruby methods' do 5 | out = get('/Array/max') 6 | 7 | expect(out).to have_received(:puts).with('Content-Type: application/json') 8 | expect(out).to have_received(:puts).with('["max","max_by"]') 9 | end 10 | 11 | def get(path) 12 | output = object_spy($stdout) 13 | RubyDocServer.new(output: output).process_request(path) 14 | output 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /04-acceptance-specs/02/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec 5 | F 6 | 7 | Failures: 8 | 9 | 1) Expense Tracker API records submitted expenses 10 | Failure/Error: ExpenseTracker::API.new 11 |  12 | NameError: 13 |  uninitialized constant ExpenseTracker::API 14 | 15 | truncated 16 | 17 | -------------------------------------------------------------------------------- /07-structuring-code-examples/solutions/shared_examples_exercise/spec/addressable_spec.rb: -------------------------------------------------------------------------------- 1 | require 'addressable' 2 | require 'support/shared_examples' 3 | 4 | RSpec.describe Addressable do 5 | it_behaves_like 'a URI parsing library', Addressable::URI 6 | 7 | it 'does not default the port for an http URI' do 8 | expect(Addressable::URI.parse('http://example.com/').port).to eq nil 9 | end 10 | 11 | it 'does not default the port for an https URI' do 12 | expect(Addressable::URI.parse('https://example.com/').port).to eq nil 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /A1-rspec-and-wider-ecosystem/02/expense_tracker/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | RSpec::Core::RakeTask.new(:spec) 3 | 4 | require 'rspec/core/rake_task' 5 | 6 | namespace :spec do 7 | desc 'Runs unit specs' 8 | RSpec::Core::RakeTask.new(:unit) do |t| 9 | t.pattern = 'spec/unit/**/*_spec.rb' 10 | t.rspec_opts = ['--profile'] 11 | end 12 | end 13 | 14 | begin 15 | require 'rspec/core/rake_task' 16 | RSpec::Core::RakeTask.new(:spec) 17 | rescue LoadError 18 | puts 'Spec tasks not defined since RSpec is unavailable' 19 | end 20 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/07/sessions/improved_matcher_failure.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1) `have_a_balance_of(amount)` fails when the balance does not match 5 | Failure/Error: expect(account).to have_a_balance_of(35) 6 |  expected # to have a balance of 35, but had a ↩ 7 | balance of 30 8 | # ./spec/initial_account_spec.rb:17:in `block (2 levels) in 10 | -------------------------------------------------------------------------------- /13-understanding-test-doubles/02/expense_tracker/app/api.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require 'json' 3 | 4 | module ExpenseTracker 5 | class API < Sinatra::Base 6 | def initialize(ledger: Ledger.new) 7 | @ledger = ledger 8 | super() 9 | end 10 | 11 | post '/expenses' do 12 | expense = JSON.parse(request.body.read) 13 | result = @ledger.record(expense) 14 | JSON.generate('expense_id' => result.expense_id) 15 | end 16 | 17 | get '/expenses/:date' do 18 | JSON.generate([]) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /06-integration-specs/06/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec spec/integration/app/ledger_spec.rb 5 | 6 | Randomized with seed 49398 7 | 8 | ExpenseTracker::Ledger 9 | #record 10 | with a valid expense 11 |  successfully saves the expense in the DB 12 | 13 | Finished in 0.01121 seconds (files took 0.15597 seconds to load) 14 | 1 example, 0 failures 15 | 16 | Randomized with seed 49398 17 | 18 | -------------------------------------------------------------------------------- /08-metadata/10/spec/music_storage_spec.rb: -------------------------------------------------------------------------------- 1 | class S3Client 2 | def self.for(type) 3 | return if type == :real || type == :memory 4 | raise "Invalid: #{type.inspect}" 5 | end 6 | end 7 | 8 | RSpec.describe 'Music storage' do 9 | let(:s3_client) do |example| 10 | S3Client.for(example.metadata[:s3_adapter]) 11 | end 12 | 13 | it 'stores music on the real S3', s3_adapter: :real do 14 | # ... 15 | end 16 | 17 | it 'stores music on an in-memory S3', s3_adapter: :memory do 18 | # ... 19 | end 20 | 21 | before { s3_client } 22 | end 23 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/01/password_strength_validator/spec/password_strength_validator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'password_strength_validator' 2 | 3 | RSpec.describe PasswordStrengthValidator do 4 | it 'rejects passwords shorter than 8 characters' do 5 | validator = PasswordStrengthValidator.new('a8E^rd2') 6 | expect(validator.strong_enough?).to eq false 7 | end 8 | 9 | it 'accepts passwords 8 characters or longer' do 10 | validator = PasswordStrengthValidator.new('a8E^rd2i') 11 | expect(validator.strong_enough?).to eq true 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /09-configuring-rspec/04/configuring_rspec/mocha_spec.rb: -------------------------------------------------------------------------------- 1 | class PointOfSale 2 | def self.purchase(item, with: nil) 3 | with.charge(item.cost) 4 | end 5 | end 6 | 7 | RSpec.configure do |config| 8 | config.mock_with :mocha 9 | end 10 | 11 | RSpec.describe 'config.mock_with :mocha' do 12 | it 'allows you to use mocha instead of rspec-mocks' do 13 | item = stub('Book', cost: 17.50) 14 | 15 | credit_card = mock('CreditCard') 16 | credit_card.expects(:charge).with(17.50) 17 | 18 | PointOfSale.purchase(item, with: credit_card) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /06-integration-specs/08/expense_tracker/app/ledger.rb: -------------------------------------------------------------------------------- 1 | module ExpenseTracker 2 | RecordResult = Struct.new(:success?, :expense_id, :error_message) 3 | 4 | class Ledger 5 | def record(expense) 6 | unless expense.key?('payee') 7 | message = 'Invalid expense: `payee` is required' 8 | return RecordResult.new(false, nil, message) 9 | end 10 | 11 | DB[:expenses].insert(expense) 12 | id = DB[:expenses].max(:id) 13 | RecordResult.new(true, id, nil) 14 | end 15 | 16 | def expenses_on(date) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /10-exploring-rspec-expectations/06/spec/cookie_recipe_spec.rb: -------------------------------------------------------------------------------- 1 | class CookieRecipe 2 | attr_reader :ingredients 3 | 4 | def initialize 5 | @ingredients = [:butter, :milk, :flour, :sugar, :eggs, :chocolate_chips] 6 | end 7 | end 8 | 9 | RSpec.describe CookieRecipe, '#ingredients' do 10 | it 'should include :butter, :milk and :eggs' do 11 | expect(CookieRecipe.new.ingredients).to include(:butter, :milk, :eggs) 12 | end 13 | 14 | it 'should not include :fish_oil' do 15 | expect(CookieRecipe.new.ingredients).not_to include(:fish_oil) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /05-unit-specs/04/expense_tracker/app/api.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require 'json' 3 | 4 | module ExpenseTracker 5 | class API < Sinatra::Base 6 | def initialize(ledger: Ledger.new) 7 | @ledger = ledger 8 | super() 9 | end 10 | 11 | post '/expenses' do 12 | status 404 13 | 14 | expense = JSON.parse(request.body.read) 15 | result = @ledger.record(expense) 16 | JSON.generate('expense_id' => result.expense_id) 17 | end 18 | 19 | 20 | get '/expenses/:date' do 21 | JSON.generate([]) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /02-running-specs/01/spec/coffee_spec.rb: -------------------------------------------------------------------------------- 1 | class Coffee 2 | def ingredients 3 | @ingredients ||= [] 4 | end 5 | 6 | def add(ingredient) 7 | ingredients << ingredient 8 | end 9 | 10 | def price 11 | 1.00 12 | end 13 | end 14 | 15 | RSpec.describe 'A cup of coffee' do 16 | let(:coffee) { Coffee.new } 17 | 18 | it 'costs $1' do 19 | expect(coffee.price).to eq(1.00) 20 | end 21 | 22 | context 'with milk' do 23 | before { coffee.add :milk } 24 | 25 | it 'costs $1.25' do 26 | expect(coffee.price).to eq(1.25) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/20/ruby_doc_server/spec/ruby_doc_server_spec.rb: -------------------------------------------------------------------------------- 1 | require 'ruby_doc_server' 2 | require 'stringio' 3 | 4 | RSpec.describe RubyDocServer do 5 | it 'finds matching ruby methods' do 6 | result = get('/Array/min') 7 | 8 | expect(result.split("\n")).to eq [ 9 | 'Content-Type: application/json', 10 | '', 11 | '["min","min_by","minmax","minmax_by"]' 12 | ] 13 | end 14 | 15 | def get(path) 16 | output = StringIO.new 17 | RubyDocServer.new(output: output).process_request(path) 18 | output.string 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/22/sales_tax/spec/unit/invoice_spec.rb: -------------------------------------------------------------------------------- 1 | require 'invoice' 2 | 3 | RSpec.describe Invoice do 4 | let(:address) { Address.new(zip: '90210') } 5 | let(:items) { [Item.new(cost: 30), Item.new(cost: 70)] } 6 | 7 | it 'calculates the total' do 8 | tax_rate = instance_double(Taxjar::Rate, combined_rate: 0.095) 9 | tax_client = instance_double(Taxjar::Client, rates_for_location: tax_rate) 10 | 11 | invoice = Invoice.new(address, items, tax_client: tax_client) 12 | 13 | expect(invoice.calculate_total).to eq(109.50) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /07-structuring-code-examples/10/shared_examples/spec/hash_kv_store_spec.rb: -------------------------------------------------------------------------------- 1 | require 'hash_kv_store' 2 | 3 | RSpec.describe HashKVStore do 4 | let(:kv_store) { HashKVStore.new } 5 | 6 | it 'allows you to fetch previously stored values' do 7 | kv_store.store(:language, 'Ruby') 8 | kv_store.store(:os, 'linux') 9 | 10 | expect(kv_store.fetch(:language)).to eq 'Ruby' 11 | expect(kv_store.fetch(:os)).to eq 'linux' 12 | end 13 | 14 | it 'raises a KeyError when you fetch an unknown key' do 15 | expect { kv_store.fetch(:foo) }.to raise_error(KeyError) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /07-structuring-code-examples/11/shared_examples/spec/support/kv_store_shared_examples.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples 'KV store' do |kv_store_class| 2 | let(:kv_store) { kv_store_class.new } 3 | 4 | it 'allows you to fetch previously stored values' do 5 | kv_store.store(:language, 'Ruby') 6 | kv_store.store(:os, 'linux') 7 | 8 | expect(kv_store.fetch(:language)).to eq 'Ruby' 9 | expect(kv_store.fetch(:os)).to eq 'linux' 10 | end 11 | 12 | it 'raises a KeyError when you fetch an unknown key' do 13 | expect { kv_store.fetch(:foo) }.to raise_error(KeyError) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /09-configuring-rspec/04/configuring_rspec/expect_with_spec.rb: -------------------------------------------------------------------------------- 1 | require 'wrong' 2 | 3 | RSpec.configure do |config| 4 | config.expect_with :minitest, :rspec, Wrong 5 | end 6 | 7 | RSpec.describe 'Using different assertion/expectation libraries' do 8 | let(:result) { 2 + 2 } 9 | 10 | it 'works with minitest assertions' do 11 | assert_equal 4, result 12 | end 13 | 14 | it 'works with rspec expectations' do 15 | expect(result).to eq 4 16 | end 17 | 18 | it 'works with wrong' do 19 | # "Where 2 and 2 always makes 5..." 20 | assert { result == 5 } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/solutions/custom_matcher/spec/support/event_matchers.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :have_no_tickets_sold do 2 | match { |event| event.tickets_sold.count.zero? } 3 | 4 | failure_message do |event| 5 | super() + ", but had #{event.tickets_sold.count}" 6 | end 7 | end 8 | 9 | RSpec::Matchers.define :be_sold_out do 10 | match { |event| event.tickets_sold.count == event.capacity } 11 | 12 | failure_message do |event| 13 | unsold_count = event.capacity - event.tickets_sold.count 14 | super() + ", but had #{unsold_count} unsold tickets" 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /04-acceptance-specs/03/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec 5 | F 6 | 7 | Failures: 8 | 9 | 1) Expense Tracker API records submitted expenses 10 | Failure/Error: expect(last_response.status).to eq(200) 11 |  12 |  expected: 200 13 |  got: 404 14 |  15 |  (compared using ==) 16 | 17 | truncated 18 | 19 | -------------------------------------------------------------------------------- /04-acceptance-specs/05/sessions/rspec_runs/3.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec 5 | F 6 | 7 | Failures: 8 | 9 | 1) Expense Tracker API records submitted expenses 10 | Failure/Error: expect(last_response.status).to eq(200) 11 |  12 |  expected: 200 13 |  got: 404 14 |  15 |  (compared using ==) 16 | 17 | truncated 18 | 19 | -------------------------------------------------------------------------------- /10-exploring-rspec-expectations/02/good_failure_messages.rb: -------------------------------------------------------------------------------- 1 | # validate me 2 | require 'rspec/expectations' 3 | include RSpec::Matchers 4 | 5 | ENME = RSpec::Expectations::ExpectationNotMetError 6 | 7 | def self.heredoc_without_markers(heredoc) 8 | heredoc.chomp.split("\n").reject { |l| l =~ /# (START|END)/ }.join("\n") 9 | end 10 | 11 | expect { 12 | expect([13, 2, 3, 99]).to all be_odd 13 | }.to raise_error(ENME, heredoc_without_markers(<<-EOM)) 14 | expected [13, 2, 3, 99] to all be odd 15 | 16 | object at index 1 failed to match: 17 | expected `2.odd?` to return true, got false 18 | EOM 19 | -------------------------------------------------------------------------------- /05-unit-specs/02/expense_tracker/spec/unit/app/api_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../app/api' 2 | 3 | module ExpenseTracker 4 | RSpec.describe API do 5 | describe 'POST /expenses' do 6 | context 'when the expense is successfully recorded' do 7 | it 'returns the expense id' 8 | it 'responds with a 200 (OK)' 9 | end 10 | 11 | # ... next context will go here... 12 | 13 | context 'when the expense fails validation' do 14 | it 'returns an error message' 15 | it 'responds with a 422 (Unprocessable entity)' 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /04-acceptance-specs/02/expense_tracker/spec/acceptance/expense_tracker_api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rack/test' 2 | require 'json' 3 | require_relative '../../app/api' 4 | 5 | module ExpenseTracker 6 | RSpec.describe 'Expense Tracker API' do 7 | include Rack::Test::Methods 8 | 9 | def app 10 | ExpenseTracker::API.new 11 | end 12 | 13 | it 'records submitted expenses' do 14 | coffee = { 15 | 'payee' => 'Starbucks', 16 | 'amount' => 5.75, 17 | 'date' => '2017-06-10' 18 | } 19 | 20 | post '/expenses', JSON.generate(coffee) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /04-acceptance-specs/06/sessions/boot_app.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rackup 5 | [2017-06-13 13:34:10] INFO WEBrick 1.3.1 6 | [2017-06-13 13:34:10] INFO ruby 2.4.1 (2017-03-22) [x86_64-darwin15] 7 | [2017-06-13 13:34:10] INFO WEBrick::HTTPServer#start: pid=45203 port=9292 8 | ::1 - - [13/Jun/2017:13:34:10 -0700] "GET /expenses/2017-06-10 HTTP/1.1" ↩ 9 | 200 2 0.0046 10 | [2017-06-13 13:34:10] INFO going to shutdown ... 11 | [2017-06-13 13:34:10] INFO WEBrick::HTTPServer#start done. 12 | 13 | -------------------------------------------------------------------------------- /07-structuring-code-examples/exercises/shared_examples_exercise/spec/uri_spec.rb: -------------------------------------------------------------------------------- 1 | require 'uri' 2 | 3 | RSpec.describe URI do 4 | it 'parses the host' do 5 | expect(URI.parse('http://foo.com/').host).to eq 'foo.com' 6 | end 7 | 8 | it 'parses the port' do 9 | expect(URI.parse('http://example.com:9876').port).to eq 9876 10 | end 11 | 12 | it 'defaults the port for an http URI to 80' do 13 | expect(URI.parse('http://example.com/').port).to eq 80 14 | end 15 | 16 | it 'defaults the port for an https URI to 443' do 17 | expect(URI.parse('https://example.com/').port).to eq 443 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/06/custom_matcher/spec/initial_account_spec.rb: -------------------------------------------------------------------------------- 1 | require 'account' 2 | require 'support/matchers' 3 | 4 | RSpec.describe '`have_a_balance_of(amount)`' do 5 | let(:account) { Account.new('Checking') } 6 | 7 | before do 8 | account.expenses << Expense.new(Date.new(2017, 6, 10), 10) 9 | account.expenses << Expense.new(Date.new(2017, 6, 15), 20) 10 | end 11 | 12 | it 'passes when the balance matches' do 13 | expect(account).to have_a_balance_of(30) 14 | end 15 | 16 | it 'fails when the balance does not match' do 17 | expect(account).to have_a_balance_of(35) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /08-metadata/exercises/expense_tracker/spec/support/db.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |c| 2 | # ... 3 | 4 | c.before(:suite) do 5 | Sequel.extension :migration 6 | Sequel::Migrator.run(DB, 'db/migrations') 7 | DB[:expenses].truncate 8 | 9 | FileUtils.mkdir_p('log') 10 | require 'logger' 11 | DB.loggers << Logger.new('log/sequel.log') 12 | end 13 | 14 | c.around(:example, :db) do |example| 15 | DB.log_info "Starting example: #{example.metadata[:description]}" 16 | DB.transaction(rollback: :always) { example.run } 17 | DB.log_info "Ending example: #{example.metadata[:description]}" 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/06/custom_matcher/lib/account.rb: -------------------------------------------------------------------------------- 1 | require 'date' 2 | 3 | Account = Struct.new(:name) do 4 | def expenses 5 | @expenses ||= [] 6 | end 7 | 8 | def current_balance 9 | calculate_balance(expenses) 10 | end 11 | 12 | def balance_as_of(date) 13 | calculate_balance(expenses.select { |e| e.date <= date }) 14 | end 15 | 16 | def to_s 17 | super.sub('struct ', '') 18 | end 19 | alias_method :inspect, :to_s 20 | 21 | private 22 | 23 | def calculate_balance(expenses) 24 | expenses.map(&:amount).inject(0, :+) 25 | end 26 | end 27 | 28 | Expense = Struct.new(:date, :amount) 29 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/03/daily_summary_email/lib/daily_summary.rb: -------------------------------------------------------------------------------- 1 | class DailySummary 2 | def send_daily_summary(user_email, todays_messages) 3 | message_count = todays_messages.count 4 | thread_count = todays_messages.map { |m| m[:thread_id] }.uniq.count 5 | subject = 'Your daily message summary' 6 | body = "You missed #{message_count} messages " \ 7 | "in #{thread_count} threads today" 8 | 9 | deliver(email: user_email, subject: subject, body: body) 10 | end 11 | 12 | def deliver(email:, subject:, body:) 13 | # send the message via SMTP 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /08-metadata/11/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | if ENV['SIMULATE_JRUBY'] 2 | original_ruby_platform = RUBY_PLATFORM 3 | Object.send(:remove_const, :RUBY_PLATFORM) 4 | RUBY_PLATFORM = 'java' 5 | end 6 | 7 | RSpec.configure do |config| 8 | config.filter_run_excluding :jruby_only unless RUBY_PLATFORM == 'java' 9 | end 10 | 11 | if ENV['SIMULATE_JRUBY'] 12 | Object.send(:remove_const, :RUBY_PLATFORM) 13 | RUBY_PLATFORM = original_ruby_platform 14 | end 15 | 16 | RSpec.describe "All rubies" do 17 | it "always runs" do 18 | end 19 | end 20 | 21 | RSpec.describe "Only on JRuby", :jruby_only do 22 | it "always runs" do 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /07-structuring-code-examples/11/sessions/rspec_runs/3.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec spec/include_examples_twice_spec.rb --format documentation 5 | 6 | Key-value stores 7 |  allows you to fetch previously stored values 8 |  raises a KeyError when you fetch an unknown key 9 |  allows you to fetch previously stored values 10 |  raises a KeyError when you fetch an unknown key 11 | 12 | Finished in 0.00355 seconds (files took 0.10257 seconds to load) 13 | 4 examples, 0 failures 14 | 15 | -------------------------------------------------------------------------------- /07-structuring-code-examples/solutions/shared_examples_exercise/spec/support/shared_examples.rb: -------------------------------------------------------------------------------- 1 | require 'uri' 2 | 3 | RSpec.shared_examples 'a URI parsing library' do |library| 4 | it 'parses the scheme' do 5 | expect(library.parse('https://a.com/').scheme).to eq 'https' 6 | end 7 | 8 | it 'parses the host' do 9 | expect(library.parse('http://foo.com/').host).to eq 'foo.com' 10 | end 11 | 12 | it 'parses the port' do 13 | expect(library.parse('http://example.com:9876').port).to eq 9876 14 | end 15 | 16 | it 'parses the path' do 17 | expect(library.parse('http://a.com/foo').path).to eq '/foo' 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /06-integration-specs/09/sessions/curl_output_success.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ curl localhost:9292/expenses --data '{"payee":"Zoo", "amount":10, ↩ 5 | "date":"2017-06-10"}' -w "\n" 6 | {"expense_id":1} 7 | $ curl localhost:9292/expenses --data '{"payee":"Starbucks", "amount":7.5, ↩ 8 | "date":"2017-06-10"}' -w "\n" 9 | {"expense_id":2} 10 | $ curl localhost:9292/expenses/2017-06-10 -w "\n" 11 | [{"id":1,"payee":"Zoo","amount":10.0,"date":"2017-06-10"},{"id":2,"payee":"St 12 | arbucks","amount":7.5,"date":"2017-06-10"}] 13 | 14 | -------------------------------------------------------------------------------- /07-structuring-code-examples/exercises/shared_examples_exercise/spec/addressable_spec.rb: -------------------------------------------------------------------------------- 1 | require 'addressable' 2 | 3 | RSpec.describe Addressable do 4 | it 'parses the scheme' do 5 | expect(Addressable::URI.parse('https://a.com/').scheme).to eq 'https' 6 | end 7 | 8 | it 'parses the host' do 9 | expect(Addressable::URI.parse('https://foo.com/').host).to eq 'foo.com' 10 | end 11 | 12 | it 'parses the port' do 13 | expect(Addressable::URI.parse('http://example.com:9876').port).to eq 9876 14 | end 15 | 16 | it 'parses the path' do 17 | expect(Addressable::URI.parse('http://a.com/foo').path).to eq '/foo' 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /02-running-specs/03/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec --profile 2 5 | ..... 6 | 7 | Top 2 slowest examples (0.90618 seconds, 59.9% of total time): 8 | The sleep() method can sleep for 0.5 second 9 | 0.50118 seconds ./spec/slow_spec.rb:6 10 | The sleep() method can sleep for 0.4 second 11 | 0.40501 seconds ./spec/slow_spec.rb:5 12 | 13 | Finished in 1.51 seconds (files took 0.08911 seconds to load) 14 | 5 examples, 0 failures 15 | 16 | -------------------------------------------------------------------------------- /04-acceptance-specs/03/expense_tracker/spec/acceptance/expense_tracker_api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rack/test' 2 | require 'json' 3 | require_relative '../../app/api' 4 | 5 | module ExpenseTracker 6 | RSpec.describe 'Expense Tracker API' do 7 | include Rack::Test::Methods 8 | 9 | def app 10 | ExpenseTracker::API.new 11 | end 12 | 13 | it 'records submitted expenses' do 14 | coffee = { 15 | 'payee' => 'Starbucks', 16 | 'amount' => 5.75, 17 | 'date' => '2017-06-10' 18 | } 19 | 20 | post '/expenses', JSON.generate(coffee) 21 | expect(last_response.status).to eq(200) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /06-integration-specs/08/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec --seed 32043 5 | truncated 6 | 7 | Finished in 0.05786 seconds (files took 0.22818 seconds to load) 8 | 11 examples, 1 failure, 1 pending 9 | 10 | Failed examples: 11 | 12 | rspec ./spec/integration/app/ledger_spec.rb:33 # ↩ 13 | ExpenseTracker::Ledger#record when the expense lacks a payee rejects the ↩ 14 | expense as invalid 15 | 16 | Randomized with seed 32043 17 | 18 | -------------------------------------------------------------------------------- /06-integration-specs/09/expense_tracker/app/ledger.rb: -------------------------------------------------------------------------------- 1 | require_relative '../config/sequel' 2 | 3 | module ExpenseTracker 4 | RecordResult = Struct.new(:success?, :expense_id, :error_message) 5 | 6 | class Ledger 7 | def record(expense) 8 | unless expense.key?('payee') 9 | message = 'Invalid expense: `payee` is required' 10 | return RecordResult.new(false, nil, message) 11 | end 12 | 13 | DB[:expenses].insert(expense) 14 | id = DB[:expenses].max(:id) 15 | RecordResult.new(true, id, nil) 16 | end 17 | 18 | def expenses_on(date) 19 | DB[:expenses].where(date: date).all 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/09/sessions/composed_matcher_failure.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1) `have_a_balance_of(amount)` fails when the balance does not match 5 | Failure/Error: expect(account).to ↩ 6 | have_a_balance_of(a_value_within(1).of(10_499_999)) 7 |  expected # to have a balance of a value ↩ 8 | within 1 of 10499999, but had a balance of 10500000.9 9 | # ./spec/composed_account_spec.rb:19:in `block (2 levels) in 11 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/02/password_strength_validator/spec/password_strength_validator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'password_strength_validator' 2 | 3 | RSpec.describe PasswordStrengthValidator do 4 | before do 5 | allow(Acme::Config).to receive(:min_password_length).and_return(6) 6 | end 7 | 8 | it 'rejects passwords shorter than the configured length' do 9 | validator = PasswordStrengthValidator.new('a8E^r') 10 | expect(validator.strong_enough?).to eq false 11 | end 12 | 13 | it 'accepts passwords that satisfy the configured length' do 14 | validator = PasswordStrengthValidator.new('a8E^rd') 15 | expect(validator.strong_enough?).to eq true 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/04/daily_summary_email/lib/daily_summary.rb: -------------------------------------------------------------------------------- 1 | require 'email_sender' 2 | 3 | class DailySummary 4 | def initialize(email_sender: EmailSender.new) 5 | @email_sender = email_sender 6 | end 7 | 8 | def send_daily_summary(user_email, todays_messages) 9 | message_count = todays_messages.count 10 | thread_count = todays_messages.map { |m| m[:thread_id] }.uniq.count 11 | subject = 'Your daily message summary' 12 | body = "You missed #{message_count} messages " \ 13 | "in #{thread_count} threads today" 14 | 15 | @email_sender.deliver(email: user_email, subject: subject, body: body) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /04-acceptance-specs/01/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec 5 | F 6 | 7 | Failures: 8 | 9 | 1) Expense Tracker API records submitted expenses 10 | Failure/Error: post '/expenses', JSON.generate(coffee) 11 |  12 | NameError: 13 |  undefined local variable or method `app' for ↩ 14 | # 15 | 16 | truncated 17 | 18 | -------------------------------------------------------------------------------- /05-unit-specs/07/expense_tracker/app/api.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require 'json' 3 | 4 | module ExpenseTracker 5 | class API < Sinatra::Base 6 | def initialize(ledger: Ledger.new) 7 | @ledger = ledger 8 | super() 9 | end 10 | 11 | post '/expenses' do 12 | expense = JSON.parse(request.body.read) 13 | result = @ledger.record(expense) 14 | 15 | if result.success? 16 | JSON.generate('expense_id' => result.expense_id) 17 | else 18 | status 422 19 | JSON.generate('error' => result.error_message) 20 | end 21 | end 22 | 23 | get '/expenses/:date' do 24 | JSON.generate([]) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /07-structuring-code-examples/11/sessions/rspec_runs/4.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec spec/it_behaves_like_twice_spec.rb --format documentation 5 | 6 | Key-value stores 7 | behaves like KV store 8 |  allows you to fetch previously stored values 9 |  raises a KeyError when you fetch an unknown key 10 | behaves like KV store 11 |  allows you to fetch previously stored values 12 |  raises a KeyError when you fetch an unknown key 13 | 14 | Finished in 0.00337 seconds (files took 0.09726 seconds to load) 15 | 4 examples, 0 failures 16 | 17 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/03/daily_summary_email/spec/daily_summary_spec.rb: -------------------------------------------------------------------------------- 1 | require 'daily_summary' 2 | 3 | RSpec.describe DailySummary do 4 | let(:todays_messages) do 5 | [ 6 | { thread_id: 1, content: 'Hello world' }, 7 | { thread_id: 2, content: 'I think forums are great' }, 8 | { thread_id: 2, content: 'Me too!' } 9 | ] 10 | end 11 | 12 | it "sends a summary of today's messages and threads" do 13 | summary = DailySummary.new 14 | 15 | expect(summary).to receive(:deliver).with( 16 | hash_including(body: 'You missed 3 messages in 2 threads today') 17 | ) 18 | 19 | summary.send_daily_summary('user@example.com', todays_messages) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /A1-rspec-and-wider-ecosystem/03/minitest_with_rspec/dinosaur_test.rb: -------------------------------------------------------------------------------- 1 | class Dinosaur 2 | def fly(rocket) 3 | rocket.launch! 4 | @excited = true 5 | end 6 | 7 | def excited? 8 | @excited 9 | end 10 | end 11 | 12 | class Rocket 13 | def launch! 14 | end 15 | end 16 | 17 | require 'minitest/autorun' 18 | 19 | require 'rspec/mocks/minitest_integration' 20 | require 'rspec/expectations/minitest_integration' 21 | 22 | class DinosaurTest < Minitest::Test 23 | def test_dinosaurs_fly_rockets 24 | dinosaur = Dinosaur.new 25 | rocket = instance_double(Rocket) 26 | expect(rocket).to receive(:launch!) 27 | dinosaur.fly(rocket) 28 | expect(dinosaur).to be_excited 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /05-unit-specs/08/expense_tracker/app/api.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require 'json' 3 | require_relative 'ledger' 4 | 5 | module ExpenseTracker 6 | class API < Sinatra::Base 7 | def initialize(ledger: Ledger.new) 8 | @ledger = ledger 9 | super() 10 | end 11 | 12 | post '/expenses' do 13 | expense = JSON.parse(request.body.read) 14 | result = @ledger.record(expense) 15 | 16 | if result.success? 17 | JSON.generate('expense_id' => result.expense_id) 18 | else 19 | status 422 20 | JSON.generate('error' => result.error_message) 21 | end 22 | end 23 | 24 | get '/expenses/:date' do 25 | JSON.generate([]) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /02-running-specs/06/spec/coffee_spec.rb: -------------------------------------------------------------------------------- 1 | class Coffee 2 | def ingredients 3 | @ingredients ||= [] 4 | end 5 | 6 | def add(ingredient) 7 | ingredients << ingredient 8 | end 9 | 10 | def price 11 | 1.00 + ingredients.size * 0.25 12 | end 13 | end 14 | 15 | RSpec.configure do |config| 16 | config.example_status_persistence_file_path = 'spec/examples.txt' 17 | end 18 | 19 | RSpec.describe 'A cup of coffee' do 20 | let(:coffee) { Coffee.new } 21 | 22 | it 'costs $1' do 23 | expect(coffee.price).to eq(1.00) 24 | end 25 | 26 | context 'with milk' do 27 | before { coffee.add :milk } 28 | 29 | it 'costs $1.25' do 30 | expect(coffee.price).to eq(1.25) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /11-matchers-included-in-rspec-expectations/solutions/phone_number_extractor_spec.rb: -------------------------------------------------------------------------------- 1 | class PhoneNumberExtractor 2 | def self.extract_from(text, &block) 3 | # Look for patterns like (###) ###-#### 4 | text.scan(/(d{3}) d{3}-d{4}/, &block) 5 | end 6 | end 7 | 8 | RSpec.describe PhoneNumberExtractor do 9 | let(:text) do 10 | <<-EOS 11 | Melinda: (202) 555-0168 12 | Bob: 202-555-0199 13 | Sabina: (202) 555-0176 14 | EOS 15 | end 16 | 17 | it 'yields phone numbers as it finds them' do 18 | expect { |probe| 19 | PhoneNumberExtractor.extract_from(text, &probe) 20 | }.to yield_successive_args( 21 | '(202) 555-0168', '202-555-0199', '(202) 555-0175' 22 | ) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /05-unit-specs/07/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec spec/unit/app/api_spec.rb 5 | 6 | Randomized with seed 33950 7 | 8 | ExpenseTracker::API 9 | POST /expenses 10 | when the expense is successfully recorded 11 |  returns the expense id 12 |  responds with a 200 (OK) 13 | when the expense fails validation 14 |  returns an error message 15 |  responds with a 422 (Unprocessable entity) 16 | 17 | Finished in 0.02529 seconds (files took 0.13831 seconds to load) 18 | 4 examples, 0 failures 19 | 20 | Randomized with seed 33950 21 | 22 | -------------------------------------------------------------------------------- /01-getting-started/06/sessions/fail_toppings.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec 5 | .F 6 | 7 | Failures: 8 | 9 | 1) An ideal sandwich lets me add toppings 10 | Failure/Error: expect(toppings).not_to be_empty 11 |  expected `[].empty?` to return false, got true 12 | # ./spec/sandwich_spec.rb:18:in `block (2 levels) in ' 13 | 14 | Finished in 0.0116 seconds (files took 0.08146 seconds to load) 15 | 2 examples, 1 failure 16 | 17 | Failed examples: 18 | 19 | rspec ./spec/sandwich_spec.rb:14 # An ideal sandwich lets me add toppings 20 | 21 | -------------------------------------------------------------------------------- /05-unit-specs/12/expense_tracker/app/api.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require 'json' 3 | require_relative 'ledger' 4 | 5 | module ExpenseTracker 6 | class API < Sinatra::Base 7 | def initialize(ledger: Ledger.new) 8 | @ledger = ledger 9 | super() 10 | end 11 | 12 | post '/expenses' do 13 | expense = JSON.parse(request.body.read) 14 | result = @ledger.record(expense) 15 | 16 | if result.success? 17 | JSON.generate('expense_id' => result.expense_id) 18 | else 19 | status 422 20 | JSON.generate('error' => result.error_message) 21 | end 22 | end 23 | 24 | get '/expenses/:date' do 25 | JSON.generate(@ledger.expenses_on(params[:date])) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/19/ruby_doc_server/lib/ruby_doc_server.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | class RubyDocServer 4 | def initialize(output: $stdout) 5 | @output = output 6 | end 7 | 8 | def process_request(path) 9 | class_name, method_prefix = path.sub(%r{^/}, '').split('/') 10 | klass = Object.const_get(class_name) 11 | methods = klass.instance_methods.grep(/\A#{method_prefix}/).sort 12 | respond_with(methods) 13 | end 14 | 15 | private 16 | 17 | def respond_with(data) 18 | @output.puts 'Content-Type: application/json' 19 | @output.puts 20 | @output.puts JSON.generate(data) 21 | end 22 | end 23 | 24 | if __FILE__.end_with?($PROGRAM_NAME) 25 | RubyDocServer.new.process_request(ENV['PATH_INFO']) 26 | end 27 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/08/sessions/as_of_matcher_failure.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1) `have_a_balance_of(amount)` fails when a date's balance does not match 5 | Failure/Error: expect(account).to ↩ 6 | have_a_balance_of(15).as_of(Date.new(2017, 6, 12)) 7 |  expected # to have a balance of 15 as of ↩ 8 | #, but had a ↩ 9 | balance of 10 10 | # ./spec/as_of_account_spec.rb:19:in `block (2 levels) in 12 | -------------------------------------------------------------------------------- /01-getting-started/02/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec 5 | F 6 | 7 | Failures: 8 | 9 | 1) An ideal sandwich is delicious 10 | Failure/Error: sandwich = Sandwich.new('delicious', []) 11 |  12 | NameError: 13 |  uninitialized constant Sandwich 14 | # ./spec/sandwich_spec.rb:4:in `block (2 levels) in ' 15 | 16 | Finished in 0.00076 seconds (files took 0.08517 seconds to load) 17 | 1 example, 1 failure 18 | 19 | Failed examples: 20 | 21 | rspec ./spec/sandwich_spec.rb:3 # An ideal sandwich is delicious 22 | 23 | -------------------------------------------------------------------------------- /07-structuring-code-examples/06/transit/spec/berlin_transit_ticket_refactored_spec.rb: -------------------------------------------------------------------------------- 1 | require 'berlin_transit_ticket' 2 | 3 | RSpec.describe BerlinTransitTicket do 4 | def fare_for(starting_station, ending_station) 5 | ticket = BerlinTransitTicket.new 6 | ticket.starting_station = starting_station 7 | ticket.ending_station = ending_station 8 | ticket.fare 9 | end 10 | 11 | context 'when starting in zone A and ending in zone B' do 12 | it 'costs €2.70' do 13 | expect(fare_for('Bundestag', 'Leopoldplatz')).to eq 2.7 14 | end 15 | end 16 | 17 | context 'when starting in zone A and ending in zone C' do 18 | it 'costs €3.30' do 19 | expect(fare_for('Bundestag', 'Birkenwerder')).to eq 3.3 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /02-running-specs/07/spec/coffee_spec.rb: -------------------------------------------------------------------------------- 1 | class Coffee 2 | def ingredients 3 | @ingredients ||= [] 4 | end 5 | 6 | def add(ingredient) 7 | ingredients << ingredient 8 | end 9 | 10 | def price 11 | 1.00 + ingredients.size * 0.25 12 | end 13 | end 14 | 15 | RSpec.configure do |config| 16 | config.filter_run_when_matching(focus: true) 17 | config.example_status_persistence_file_path = 'spec/examples.txt' 18 | end 19 | 20 | RSpec.describe 'A cup of coffee' do 21 | let(:coffee) { Coffee.new } 22 | 23 | it 'costs $1' do 24 | expect(coffee.price).to eq(1.00) 25 | end 26 | 27 | fcontext 'with milk' do 28 | before { coffee.add :milk } 29 | 30 | it 'costs $1.25' do 31 | expect(coffee.price).to eq(1.25) 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/09/custom_matcher/spec/composed_account_spec.rb: -------------------------------------------------------------------------------- 1 | require 'account' 2 | require 'support/matchers' 3 | 4 | RSpec.describe '`have_a_balance_of(amount)`' do 5 | let(:account) { Account.new('Checking') } 6 | 7 | before do 8 | account.expenses << Expense.new(Date.new(2017, 6, 10), 10_000_000.5) 9 | account.expenses << Expense.new(Date.new(2017, 6, 15), 500_000.4) 10 | end 11 | 12 | it 'passes when the balances match' do 13 | expect(account).to have_a_balance_of(a_value < 11_000_000) 14 | # or 15 | expect(account).to have_a_balance_of(a_value_within(50).of(10_500_000)) 16 | end 17 | 18 | it 'fails when the balance does not match' do 19 | expect(account).to have_a_balance_of(a_value_within(1).of(10_499_999)) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /02-running-specs/09/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec 5 | ..** 6 | 7 | Pending: (Failures listed here are expected and do not affect your suite's ↩ 8 | status) 9 |  10 | 1) A cup of coffee with milk is light in color 11 | # Not yet implemented 12 |  # ./spec/coffee_spec.rb:34 13 |  14 | 2) A cup of coffee with milk is cooler than 200 degrees Fahrenheit 15 | # Not yet implemented 16 |  # ./spec/coffee_spec.rb:35 17 |  18 | 19 | Finished in 0.00125 seconds (files took 0.07577 seconds to load) 20 | 4 examples, 0 failures, 2 pending 21 | 22 | -------------------------------------------------------------------------------- /04-acceptance-specs/04/expense_tracker/spec/acceptance/expense_tracker_api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rack/test' 2 | require 'json' 3 | require_relative '../../app/api' 4 | 5 | module ExpenseTracker 6 | RSpec.describe 'Expense Tracker API' do 7 | include Rack::Test::Methods 8 | 9 | def app 10 | ExpenseTracker::API.new 11 | end 12 | 13 | it 'records submitted expenses' do 14 | coffee = { 15 | 'payee' => 'Starbucks', 16 | 'amount' => 5.75, 17 | 'date' => '2017-06-10' 18 | } 19 | 20 | post '/expenses', JSON.generate(coffee) 21 | expect(last_response.status).to eq(200) 22 | 23 | parsed = JSON.parse(last_response.body) 24 | expect(parsed).to include('expense_id' => a_kind_of(Integer)) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /06-integration-specs/09/sessions/boot_app_success.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rackup 5 | [2017-06-13 13:34:49] INFO WEBrick 1.3.1 6 | [2017-06-13 13:34:49] INFO ruby 2.4.1 (2017-03-22) [x86_64-darwin15] 7 | [2017-06-13 13:34:49] INFO WEBrick::HTTPServer#start: pid=45924 port=9292 8 | ::1 - - [13/Jun/2017:13:34:49 -0700] "POST /expenses HTTP/1.1" 200 16 0.0090 9 | ::1 - - [13/Jun/2017:13:34:49 -0700] "POST /expenses HTTP/1.1" 200 16 0.0017 10 | ::1 - - [13/Jun/2017:13:34:49 -0700] "GET /expenses/2017-06-10 HTTP/1.1" ↩ 11 | 200 120 0.0020 12 | [2017-06-13 13:34:49] INFO going to shutdown ... 13 | [2017-06-13 13:34:49] INFO WEBrick::HTTPServer#start done. 14 | 15 | -------------------------------------------------------------------------------- /02-running-specs/08/spec/coffee_spec.rb: -------------------------------------------------------------------------------- 1 | class Coffee 2 | def ingredients 3 | @ingredients ||= [] 4 | end 5 | 6 | def add(ingredient) 7 | ingredients << ingredient 8 | end 9 | 10 | def price 11 | 1.00 + ingredients.size * 0.25 12 | end 13 | end 14 | 15 | RSpec.configure do |config| 16 | config.filter_run_when_matching(focus: true) 17 | config.example_status_persistence_file_path = 'spec/examples.txt' 18 | end 19 | 20 | RSpec.describe 'A cup of coffee' do 21 | let(:coffee) { Coffee.new } 22 | 23 | it 'costs $1' do 24 | expect(coffee.price).to eq(1.00) 25 | end 26 | 27 | context 'with milk', focus: true do 28 | before { coffee.add :milk } 29 | 30 | it 'costs $1.25' do 31 | expect(coffee.price).to eq(1.25) 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/04/daily_summary_email/spec/daily_summary_spec.rb: -------------------------------------------------------------------------------- 1 | require 'daily_summary' 2 | 3 | RSpec.describe DailySummary do 4 | let(:todays_messages) do 5 | [ 6 | { thread_id: 1, content: 'Hello world' }, 7 | { thread_id: 2, content: 'I think forums are great' }, 8 | { thread_id: 2, content: 'Me too!' } 9 | ] 10 | end 11 | 12 | it "sends a summary of today's messages and threads" do 13 | email_sender = instance_double(EmailSender) 14 | summary = DailySummary.new(email_sender: email_sender) 15 | 16 | expect(email_sender).to receive(:deliver).with( 17 | hash_including(body: 'You missed 3 messages in 2 threads today') 18 | ) 19 | 20 | summary.send_daily_summary('user@example.com', todays_messages) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/05/daily_summary_email/spec/daily_summary_spec.rb: -------------------------------------------------------------------------------- 1 | require 'daily_summary' 2 | 3 | RSpec.describe DailySummary do 4 | let(:todays_messages) do 5 | [ 6 | { thread_id: 1, content: 'Hello world' }, 7 | { thread_id: 2, content: 'I think forums are great' }, 8 | { thread_id: 2, content: 'Me too!' } 9 | ] 10 | end 11 | 12 | it "sends a summary of today's messages and threads" do 13 | email_sender = instance_spy(EmailSender) 14 | summary = DailySummary.new(email_sender: email_sender) 15 | 16 | summary.send_daily_summary('user@example.com', todays_messages) 17 | 18 | expect(email_sender).to have_received(:deliver).with( 19 | hash_including(body: 'You missed 3 messages in 2 threads today') 20 | ) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /06-integration-specs/09/sessions/rspec_runs/2.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec spec/integration/app/ledger_spec.rb 5 | 6 | Randomized with seed 22267 7 | 8 | ExpenseTracker::Ledger 9 | #record 10 | with a valid expense 11 |  successfully saves the expense in the DB 12 | when the expense lacks a payee 13 |  rejects the expense as invalid 14 | #expenses_on 15 |  returns all expenses for the provided date 16 |  returns a blank array when there are no matching expenses 17 | 18 | Finished in 0.01832 seconds (files took 0.16797 seconds to load) 19 | 4 examples, 0 failures 20 | 21 | Randomized with seed 22267 22 | 23 | -------------------------------------------------------------------------------- /07-structuring-code-examples/11/shared_examples/spec/pass_block_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples 'KV store' do 2 | it 'allows you to fetch previously stored values' do 3 | kv_store.store(:language, 'Ruby') 4 | kv_store.store(:os, 'linux') 5 | 6 | expect(kv_store.fetch(:language)).to eq 'Ruby' 7 | expect(kv_store.fetch(:os)).to eq 'linux' 8 | end 9 | 10 | # remainder of examples... 11 | 12 | it 'raises a KeyError when you fetch an unknown key' do 13 | expect { kv_store.fetch(:foo) }.to raise_error(KeyError) 14 | end 15 | end 16 | 17 | require 'file_kv_store' 18 | require 'tempfile' 19 | 20 | RSpec.describe FileKVStore do 21 | it_behaves_like 'KV store' do 22 | let(:tempfile) { Tempfile.new('kv.store') } 23 | let(:kv_store) { FileKVStore.new(tempfile.path) } 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /11-matchers-included-in-rspec-expectations/exercises/phone_number_extractor_spec.rb: -------------------------------------------------------------------------------- 1 | class PhoneNumberExtractor 2 | def self.extract_from(text, &block) 3 | # Look for patterns like (###) ###-#### 4 | text.scan(/(d{3}) d{3}-d{4}/, &block) 5 | end 6 | end 7 | 8 | RSpec.describe PhoneNumberExtractor do 9 | let(:text) do 10 | <<-EOS 11 | Melinda: (202) 555-0168 12 | Bob: 202-555-0199 13 | Sabina: (202) 555-0176 14 | EOS 15 | end 16 | 17 | it 'yields phone numbers as it finds them' do 18 | yielded_numbers = [] 19 | PhoneNumberExtractor.extract_from(text) do |number| 20 | yielded_numbers << number 21 | end 22 | 23 | expect(yielded_numbers).to eq [ 24 | '(202) 555-0168', 25 | '202-555-0199', 26 | '(202) 555-0175' 27 | ] 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/07/subscription_service/spec/recurring_payment_spec.rb: -------------------------------------------------------------------------------- 1 | require 'recurring_payment' 2 | 3 | Card = Struct.new(:type, :number) 4 | Subscription = Struct.new(:name, :credit_card, :amount) 5 | 6 | RSpec.describe RecurringPayment do 7 | it 'charges the credit card for each subscription' do 8 | card_1 = Card.new(:visa, '1234 5678 9012 3456') 9 | card_2 = Card.new(:mastercard, '9876 5432 1098 7654') 10 | 11 | subscriptions = [ 12 | Subscription.new('John Doe', card_1, 19.99), 13 | Subscription.new('Jane Doe', card_2, 29.99) 14 | ] 15 | 16 | expect(CashCow).to receive(:charge_card).with(card_1, 19.99) 17 | expect(CashCow).to receive(:charge_card).with(card_2, 29.99) 18 | 19 | RecurringPayment.process_subscriptions(subscriptions) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /02-running-specs/01/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec 5 | .F 6 | 7 | Failures: 8 | 9 | 1) A cup of coffee with milk costs $1.25 10 | Failure/Error: expect(coffee.price).to eq(1.25) 11 |  12 |  expected: 1.25 13 |  got: 1.0 14 |  15 |  (compared using ==) 16 | # ./spec/coffee_spec.rb:26:in `block (3 levels) in ' 17 | 18 | Finished in 0.01222 seconds (files took 0.08094 seconds to load) 19 | 2 examples, 1 failure 20 | 21 | Failed examples: 22 | 23 | rspec ./spec/coffee_spec.rb:25 # A cup of coffee with milk costs $1.25 24 | 25 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/06/subscription_service/spec/recurring_payment_spec.rb: -------------------------------------------------------------------------------- 1 | require 'recurring_payment' 2 | 3 | Card = Struct.new(:type, :number) 4 | Subscription = Struct.new(:name, :credit_card, :amount) 5 | 6 | RSpec.describe RecurringPayment do 7 | it 'charges the credit card for each subscription' do 8 | card_1 = Card.new(:visa, '1234 5678 9012 3456') 9 | card_2 = Card.new(:mastercard, '9876 5432 1098 7654') 10 | 11 | subscriptions = [ 12 | Subscription.new('John Doe', card_1, 19.99), 13 | Subscription.new('Jane Doe', card_2, 29.99) 14 | ] 15 | 16 | expect(CashCow).to receive(:charge_card).with(card_1, 19.99, :excess_arg) 17 | expect(CashCow).to receive(:charge_card).with(card_2, 29.99, :excess_arg) 18 | 19 | RecurringPayment.process_subscriptions(subscriptions) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /06-integration-specs/07/sessions/rspec_runs/3.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec 5 | truncated 6 | 7 | Finished in 0.06926 seconds (files took 0.21812 seconds to load) 8 | 11 examples, 2 failures, 1 pending 9 | 10 | Failed examples: 11 | 12 | rspec ./spec/integration/app/ledger_spec.rb:20 # ↩ 13 | ExpenseTracker::Ledger#record with a valid expense successfully saves the ↩ 14 | expense in the DB 15 | rspec ./spec/integration/app/ledger_spec.rb:34 # ↩ 16 | ExpenseTracker::Ledger#record when the expense lacks a payee rejects the ↩ 17 | expense as invalid 18 | 19 | Randomized with seed 32043 20 | 21 | -------------------------------------------------------------------------------- /07-structuring-code-examples/04/before_and_after_hooks_spec.rb: -------------------------------------------------------------------------------- 1 | module MyApp 2 | class Configuration 3 | end 4 | end 5 | 6 | RSpec.describe MyApp::Configuration do 7 | before(:example) do 8 | @original_env = ENV.to_hash 9 | end 10 | 11 | after(:example) do 12 | ENV.replace(@original_env) 13 | end 14 | 15 | it 'can mutate env' do 16 | ENV['FOO'] = '1' 17 | end 18 | 19 | it 'isolates mutations from other examples' do 20 | expect(ENV['FOO']).to eq nil 21 | end 22 | 23 | end 24 | 25 | class WebBrowser 26 | def self.launch; end 27 | 28 | def self.shutdown; end 29 | end 30 | 31 | RSpec.describe 'Web interface to my thermostat' do 32 | before(:context) do 33 | WebBrowser.launch 34 | end 35 | 36 | after(:context) do 37 | WebBrowser.shutdown 38 | end 39 | 40 | it 'passes' do 41 | end 42 | 43 | end 44 | -------------------------------------------------------------------------------- /11-matchers-included-in-rspec-expectations/exercises/sessions/calendar_spec.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec calendar_spec.rb 5 | F 6 | 7 | Failures: 8 | 9 | 1) Calendar considers sundays to be on the weekend 10 | Failure/Error: expect(sunday_date.on_weekend?).to be true 11 |  12 |  expected true 13 |  got false 14 | # ./calendar_spec.rb:13:in `block (2 levels) in ' 15 | 16 | Finished in 0.01342 seconds (files took 0.08792 seconds to load) 17 | 1 example, 1 failure 18 | 19 | Failed examples: 20 | 21 | rspec ./calendar_spec.rb:12 # Calendar considers sundays to be on the weekend 22 | 23 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/09/subscription_service/spec/recurring_payment_spec.rb: -------------------------------------------------------------------------------- 1 | require 'recurring_payment' 2 | 3 | Card = Struct.new(:type, :number) 4 | Subscription = Struct.new(:name, :credit_card, :amount) 5 | 6 | RSpec.describe RecurringPayment do 7 | it 'charges the credit card for each subscription' do 8 | card_1 = Card.new(:visa, '1234 5678 9012 3456') 9 | card_2 = Card.new(:mastercard, '9876 5432 1098 7654') 10 | 11 | subscriptions = [ 12 | Subscription.new('John Doe', card_1, 19.99), 13 | Subscription.new('Jane Doe', card_2, 29.99) 14 | ] 15 | 16 | bank = class_double(CashCow) 17 | expect(bank).to receive(:charge_card).with(card_1, 19.99) 18 | expect(bank).to receive(:charge_card).with(card_2, 29.99) 19 | 20 | RecurringPayment.process_subscriptions(subscriptions, bank: bank) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /02-running-specs/09/spec/coffee_spec.rb: -------------------------------------------------------------------------------- 1 | class Coffee 2 | def ingredients 3 | @ingredients ||= [] 4 | end 5 | 6 | def add(ingredient) 7 | ingredients << ingredient 8 | end 9 | 10 | def price 11 | 1.00 + ingredients.size * 0.25 12 | end 13 | end 14 | 15 | RSpec.configure do |config| 16 | config.filter_run_when_matching(focus: true) 17 | config.example_status_persistence_file_path = 'spec/examples.txt' 18 | end 19 | 20 | RSpec.describe 'A cup of coffee' do 21 | let(:coffee) { Coffee.new } 22 | 23 | it 'costs $1' do 24 | expect(coffee.price).to eq(1.00) 25 | end 26 | 27 | context 'with milk' do 28 | before { coffee.add :milk } 29 | 30 | it 'costs $1.25' do 31 | expect(coffee.price).to eq(1.25) 32 | end 33 | 34 | it 'is light in color' 35 | it 'is cooler than 200 degrees Fahrenheit' 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /06-integration-specs/07/sessions/rspec_runs/4.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec --seed 32043 5 | truncated 6 | 7 | Finished in 0.05941 seconds (files took 0.22746 seconds to load) 8 | 11 examples, 2 failures, 1 pending 9 | 10 | Failed examples: 11 | 12 | rspec ./spec/integration/app/ledger_spec.rb:20 # ↩ 13 | ExpenseTracker::Ledger#record with a valid expense successfully saves the ↩ 14 | expense in the DB 15 | rspec ./spec/integration/app/ledger_spec.rb:34 # ↩ 16 | ExpenseTracker::Ledger#record when the expense lacks a payee rejects the ↩ 17 | expense as invalid 18 | 19 | Randomized with seed 32043 20 | 21 | -------------------------------------------------------------------------------- /05-unit-specs/04/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec spec/unit/app/api_spec.rb 5 | 6 | Randomized with seed 55289 7 | 8 | ExpenseTracker::API 9 | POST /expenses 10 | when the expense is successfully recorded 11 |  returns the expense id 12 |  responds with a 200 (OK) 13 | when the expense fails validation 14 |  responds with a 422 (Unprocessable entity) (PENDING: Not yet ↩ 15 | implemented) 16 |  returns an error message (PENDING: Not yet implemented) 17 | 18 | truncated 19 | 20 | Finished in 0.02565 seconds (files took 0.12232 seconds to load) 21 | 4 examples, 0 failures, 2 pending 22 | 23 | Randomized with seed 55289 24 | 25 | -------------------------------------------------------------------------------- /11-matchers-included-in-rspec-expectations/solutions/sessions/calendar_spec.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec calendar_spec.rb 5 | F 6 | 7 | Failures: 8 | 9 | 1) Calendar considers sundays to be on the weekend 10 | Failure/Error: expect(sunday_date).to be_on_weekend 11 |  expected `#.on_weekend?` to return true, got false 13 | # ./calendar_spec.rb:13:in `block (2 levels) in ' 14 | 15 | Finished in 0.01206 seconds (files took 0.07922 seconds to load) 16 | 1 example, 1 failure 17 | 18 | Failed examples: 19 | 20 | rspec ./calendar_spec.rb:12 # Calendar considers sundays to be on the weekend 21 | 22 | -------------------------------------------------------------------------------- /11-matchers-included-in-rspec-expectations/solutions/public_company_spec.rb: -------------------------------------------------------------------------------- 1 | PublicCompany = Struct.new(:name, :value_per_share, :share_count) do 2 | def got_better_than_expected_revenues 3 | self.value_per_share *= rand(1.05..1.10) 4 | end 5 | 6 | def market_cap 7 | @market_cap ||= value_per_share * share_count 8 | end 9 | end 10 | 11 | RSpec.describe PublicCompany do 12 | let(:company) { PublicCompany.new('Nile', 10, 100_000) } 13 | 14 | it 'increases its market cap when it gets better than expected revenues' do 15 | expect { 16 | company.got_better_than_expected_revenues 17 | }.to change(company, :market_cap).by_at_least(50_000) 18 | end 19 | 20 | it 'provides attributes' do 21 | expect(company).to have_attributes( 22 | name: 'Nil', 23 | value_per_share: 10, 24 | share_count: 10_000, 25 | market_cap: 1_000_000 26 | ) 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /02-running-specs/01/sessions/rspec_runs/2.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec --format documentation 5 | 6 | A cup of coffee 7 |  costs $1 8 | with milk 9 |  costs $1.25 (FAILED - 1) 10 | 11 | Failures: 12 | 13 | 1) A cup of coffee with milk costs $1.25 14 | Failure/Error: expect(coffee.price).to eq(1.25) 15 |  16 |  expected: 1.25 17 |  got: 1.0 18 |  19 |  (compared using ==) 20 | # ./spec/coffee_spec.rb:26:in `block (3 levels) in ' 21 | 22 | Finished in 0.01073 seconds (files took 0.08736 seconds to load) 23 | 2 examples, 1 failure 24 | 25 | Failed examples: 26 | 27 | rspec ./spec/coffee_spec.rb:25 # A cup of coffee with milk costs $1.25 28 | 29 | -------------------------------------------------------------------------------- /02-running-specs/02/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec -fd 5 | 6 | A cup of coffee 7 |  costs $1 8 | with milk 9 |  costs $1.25 (FAILED - 1) 10 | 11 | Failures: 12 | 13 | 1) A cup of coffee with milk costs $1.25 14 | Failure/Error: expect(coffee.price).to eq(1.25) 15 |  16 |  expected: 1.25 17 |  got: 1.0 18 |  19 |  (compared using ==) 20 | # ./spec/coffee_spec.rb:26:in `block (3 levels) in ' 21 | 22 | Finished in 0.0102 seconds (files took 0.09104 seconds to load) 23 | 2 examples, 1 failure 24 | 25 | Failed examples: 26 | 27 | rspec ./spec/coffee_spec.rb:25 # A cup of coffee with milk costs $1.25 28 | 29 | -------------------------------------------------------------------------------- /06-integration-specs/09/sessions/server_failure.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rackup 5 | [2017-06-13 13:34:47] INFO WEBrick 1.3.1 6 | [2017-06-13 13:34:47] INFO ruby 2.4.1 (2017-03-22) [x86_64-darwin15] 7 | [2017-06-13 13:34:47] INFO WEBrick::HTTPServer#start: pid=45899 port=9292 8 | 2017-06-13 13:34:47 - NameError - uninitialized constant ↩ 9 | ExpenseTracker::Ledger::DB: 10 | ~/code/expense_tracker/app/ledger.rb:17:in `expenses_on' 11 | ~/code/expense_tracker/app/api.rb:25:in `block in ' 12 | 13 | truncated 14 | 15 | ::1 - - [13/Jun/2017:13:34:47 -0700] "GET /expenses/2017-06-10 HTTP/1.1" ↩ 16 | 500 6642 0.0019 17 | [2017-06-13 13:34:47] INFO going to shutdown ... 18 | [2017-06-13 13:34:47] INFO WEBrick::HTTPServer#start done. 19 | 20 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/exercises/guessing_game/lib/guessing_game.rb: -------------------------------------------------------------------------------- 1 | class GuessingGame 2 | def play 3 | @number = rand(1..100) 4 | @guess = nil 5 | 6 | 5.downto(1) do |remaining_guesses| 7 | break if @guess == @number 8 | puts "Pick a number 1-100 (#{remaining_guesses} guesses left):" 9 | @guess = gets.to_i 10 | check_guess 11 | end 12 | 13 | announce_result 14 | end 15 | 16 | private 17 | 18 | def check_guess 19 | if @guess > @number 20 | puts "#{@guess} is too high!" 21 | elsif @guess < @number 22 | puts "#{@guess} is too low!" 23 | end 24 | end 25 | 26 | def announce_result 27 | if @guess == @number 28 | puts 'You won!' 29 | else 30 | puts "You lost! The number was: #{@number}" 31 | end 32 | end 33 | end 34 | 35 | # play the game if this file is run directly 36 | GuessingGame.new.play if __FILE__.end_with?($PROGRAM_NAME) 37 | -------------------------------------------------------------------------------- /05-unit-specs/02/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec spec/unit/app/api_spec.rb 5 | 6 | Randomized with seed 34086 7 | 8 | ExpenseTracker::API 9 | POST /expenses 10 | when the expense fails validation 11 |  returns an error message (PENDING: Not yet implemented) 12 |  responds with a 422 (Unprocessable entity) (PENDING: Not yet ↩ 13 | implemented) 14 | when the expense is successfully recorded 15 |  responds with a 200 (OK) (PENDING: Not yet implemented) 16 |  returns the expense id (PENDING: Not yet implemented) 17 | 18 | truncated 19 | 20 | Finished in 0.00107 seconds (files took 0.15832 seconds to load) 21 | 4 examples, 0 failures, 4 pending 22 | 23 | Randomized with seed 34086 24 | 25 | -------------------------------------------------------------------------------- /06-integration-specs/09/sessions/rspec_runs/3.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec 5 | 6 | Randomized with seed 21580 7 | F............ 8 | 9 | Failures: 10 | 11 | 1) Expense Tracker API records submitted expenses FIXED 12 | Expected pending 'Need to persist expenses' to fail. No error was ↩ 13 | raised. 14 | # ./spec/acceptance/expense_tracker_api_spec.rb:22 15 | 16 | Finished in 0.04844 seconds (files took 0.22185 seconds to load) 17 | 13 examples, 1 failure 18 | 19 | Failed examples: 20 | 21 | rspec ./spec/acceptance/expense_tracker_api_spec.rb:22 # Expense Tracker ↩ 22 | API records submitted expenses 23 | 24 | Randomized with seed 21580 25 | 26 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/08/custom_matcher/spec/support/matchers.rb: -------------------------------------------------------------------------------- 1 | # We disable line length because the labels cause lines to be too long 2 | # but they do not render in the book so it causes no problems. 3 | RSpec::Matchers.define :have_a_balance_of do |amount| 4 | chain(:as_of) { |date| @as_of_date = date } 5 | match { |account| account_balance(account) == amount } 6 | failure_message { |account| super() + failure_reason(account) } 7 | failure_message_when_negated { |account| super() + failure_reason(account) } 8 | 9 | private 10 | 11 | def failure_reason(account) 12 | ", but had a balance of #{account_balance(account)}" 13 | end 14 | 15 | def account_balance(account) 16 | if @as_of_date 17 | account.balance_as_of(@as_of_date) 18 | else 19 | account.current_balance 20 | end 21 | end 22 | end 23 | 24 | RSpec::Matchers.alias_matcher :an_account_with_a_balance_of, 25 | :have_a_balance_of 26 | -------------------------------------------------------------------------------- /02-running-specs/04/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec -e milk -fd 5 | Run options: include {:full_description=>/milk/} 6 | 7 | A cup of coffee 8 | with milk 9 |  costs $1.25 (FAILED - 1) 10 | 11 | Failures: 12 | 13 | 1) A cup of coffee with milk costs $1.25 14 | Failure/Error: expect(coffee.price).to eq(1.25) 15 |  16 |  expected: 1.25 17 |  got: 1.0 18 |  19 |  (compared using ==) 20 | # ./spec/coffee_spec.rb:26:in `block (3 levels) in ' 21 | 22 | Finished in 0.01014 seconds (files took 0.08249 seconds to load) 23 | 1 example, 1 failure 24 | 25 | Failed examples: 26 | 27 | rspec ./spec/coffee_spec.rb:25 # A cup of coffee with milk costs $1.25 28 | 29 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/18/twitter_user_formatter/spec/integration_spec.rb: -------------------------------------------------------------------------------- 1 | require 'twitter_user_formatter' 2 | require 'dotenv' 3 | Dotenv.load('api_creds.env') 4 | 5 | RSpec.describe TwitterUserFormatter do 6 | it 'works for real' do 7 | client = new_client do |c| 8 | c.consumer_key = ENV['TWITTER_CONSUMER_KEY'] 9 | c.consumer_secret = ENV['TWITTER_CONSUMER_SECRET'] 10 | c.oauth_token = ENV['TWITTER_OAUTH_TOKEN'] 11 | c.oauth_token_secret = ENV['TWITTER_OAUTH_TOKEN_SECRET'] 12 | end 13 | 14 | user = client.user('myronmarston') 15 | formatter = TwitterUserFormatter.new(user) 16 | expect(formatter.format).to start_with "Myron Marston's website is http" 17 | end 18 | 19 | if Twitter::Version::MAJOR == 4 20 | def new_client(&block) 21 | Twitter.configure(&block) 22 | Twitter 23 | end 24 | else 25 | def new_client(&block) 26 | Twitter::REST::Client.new(&block) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /04-acceptance-specs/05/sessions/rspec_runs/4.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec 5 | F 6 | 7 | Failures: 8 | 9 | 1) Expense Tracker API records submitted expenses 10 | Failure/Error: expect(expenses).to contain_exactly(coffee, zoo) 11 |  12 |  expected collection contained: [{"payee"=>"Starbucks", ↩ 13 | "amount"=>5.75, "date"=>"2017-06-10", "id"=>42}, {"payee"=>"Zoo", ↩ 14 | "amount"=>15.25, "date"=>"2017-06-10", "id"=>42}] 15 |  actual collection contained: [] 16 |  the missing elements were: [{"payee"=>"Starbucks", ↩ 17 | "amount"=>5.75, "date"=>"2017-06-10", "id"=>42}, {"payee"=>"Zoo", ↩ 18 | "amount"=>15.25, "date"=>"2017-06-10", "id"=>42}] 19 | 20 | truncated 21 | 22 | -------------------------------------------------------------------------------- /10-exploring-rspec-expectations/03/how_matchers_work.irbr: -------------------------------------------------------------------------------- 1 | >> require 'rspec/expectations' 2 | => true 3 | >> include RSpec::Matchers 4 | => Object 5 | 6 | >> matcher = Object.new 7 | => # 8 | 9 | >> expect(1).to matcher 10 | NoMethodError: undefined method `matches?' for # 11 | backtrace truncated 12 | 13 | >> def matcher.matches?(actual) 14 | >> actual == 1 15 | >> end 16 | => :matches? 17 | >> expect(1).to matcher 18 | => true 19 | 20 | >> expect(2).to matcher 21 | NoMethodError: undefined method `failure_message' for ↩ 22 | # 23 | backtrace truncated 24 | 25 | >> def matcher.failure_message 26 | >> 'expected object to equal 1' 27 | >> end 28 | => :failure_message 29 | >> expect(2).to matcher 30 | RSpec::Expectations::ExpectationNotMetError: expected object to equal 1 31 | backtrace truncated 32 | -------------------------------------------------------------------------------- /11-matchers-included-in-rspec-expectations/solutions/sessions/water_spec.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec water_spec.rb 5 | F 6 | 7 | Failures: 8 | 9 | 1) Water is H2O 10 | Failure/Error: expect(Water.elements).to ↩ 11 | contain_exactly(:hydrogen, :hydrogen, :oxygen) 12 |  13 |  expected collection contained: [:hydrogen, :hydrogen, :oxygen] 14 |  actual collection contained: [:hydrogen, :oxygen] 15 |  the missing elements were: [:hydrogen] 16 | # ./water_spec.rb:9:in `block (2 levels) in ' 17 | 18 | Finished in 0.0115 seconds (files took 0.08187 seconds to load) 19 | 1 example, 1 failure 20 | 21 | Failed examples: 22 | 23 | rspec ./water_spec.rb:8 # Water is H2O 24 | 25 | -------------------------------------------------------------------------------- /11-matchers-included-in-rspec-expectations/exercises/public_company_spec.rb: -------------------------------------------------------------------------------- 1 | PublicCompany = Struct.new(:name, :value_per_share, :share_count) do 2 | def got_better_than_expected_revenues 3 | self.value_per_share *= rand(1.05..1.10) 4 | end 5 | 6 | def market_cap 7 | @market_cap ||= value_per_share * share_count 8 | end 9 | end 10 | 11 | RSpec.describe PublicCompany do 12 | let(:company) { PublicCompany.new('Nile', 10, 100_000) } 13 | 14 | it 'increases its market cap when it gets better than expected revenues' do 15 | before_market_cap = company.market_cap 16 | company.got_better_than_expected_revenues 17 | after_market_cap = company.market_cap 18 | 19 | expect(after_market_cap - before_market_cap).to be >= 50_000 20 | end 21 | 22 | it 'provides attributes' do 23 | expect(company.name).to eq('Nil') 24 | expect(company.value_per_share).to eq(10) 25 | expect(company.share_count).to eq(10_000) 26 | expect(company.market_cap).to eq(1_000_000) 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/01/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec 5 | .F 6 | 7 | Failures: 8 | 9 | 1) PasswordStrengthValidator accepts passwords 8 characters or longer 10 | Failure/Error: expect(validator.strong_enough?).to eq true 11 |  12 |  expected: true 13 |  got: false 14 |  15 |  (compared using ==) 16 | # ./spec/password_strength_validator_spec.rb:11:in `block (2 levels) ↩ 17 | in ' 18 | 19 | Finished in 0.01002 seconds (files took 0.08962 seconds to load) 20 | 2 examples, 1 failure 21 | 22 | Failed examples: 23 | 24 | rspec ./spec/password_strength_validator_spec.rb:9 # ↩ 25 | PasswordStrengthValidator accepts passwords 8 characters or longer 26 | 27 | -------------------------------------------------------------------------------- /06-integration-specs/09/sessions/each_spec_file_failure.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ (for f in `find spec -iname '*_spec.rb'`; do ↩ 5 | echo "$f:" ↩ 6 | bundle exec rspec $f -fp || exit 1 ↩ 7 | done) 8 | spec/acceptance/expense_tracker_api_spec.rb: 9 | 10 | Randomized with seed 24954 11 | 12 | An error occurred in a `before(:suite)` hook. 13 | Failure/Error: Sequel.extension :migration 14 |  15 | NameError: 16 |  uninitialized constant Sequel 17 | # ./spec/support/db.rb:3:in `block (2 levels) in ' 18 | 19 | 20 | Finished in 0.01902 seconds (files took 0.16858 seconds to load) 21 | 0 examples, 0 failures, 1 error occurred outside of examples 22 | 23 | Randomized with seed 24954 24 | 25 | -------------------------------------------------------------------------------- /05-unit-specs/11/expense_tracker/spec/unit/api_example_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../app/api' 2 | require 'rack/test' 3 | 4 | module ExpenseTracker 5 | RSpec.describe API do 6 | include Rack::Test::Methods 7 | 8 | def app 9 | API.new(ledger: ledger) 10 | end 11 | 12 | let(:ledger) { instance_double('ExpenseTracker::Ledger') } 13 | 14 | describe 'POST /expenses' do 15 | context 'when the expense is successfully recorded' do 16 | let(:expense) { { 'some' => 'data' } } 17 | 18 | before do 19 | allow(ledger).to receive(:record) 20 | .with(expense) 21 | .and_return(RecordResult.new(true, 417, nil)) 22 | end 23 | 24 | it 'works' do 25 | post '/expenses', JSON.generate(expense) 26 | 27 | parsed = JSON.parse(last_response.body) 28 | expect(parsed).to do_something 29 | end 30 | 31 | def do_something 32 | include('expense_id' => 417) 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /06-integration-specs/03/expense_tracker/spec/integration/app/ledger_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../app/ledger' 2 | require_relative '../../../config/sequel' 3 | require_relative '../../support/db' 4 | 5 | module ExpenseTracker 6 | RSpec.describe Ledger do 7 | let(:ledger) { Ledger.new } 8 | let(:expense) do 9 | { 10 | 'payee' => 'Starbucks', 11 | 'amount' => 5.75, 12 | 'date' => '2017-06-10' 13 | } 14 | end 15 | 16 | describe '#record' do 17 | # ... contexts go here ... 18 | 19 | context 'with a valid expense' do 20 | it 'successfully saves the expense in the DB' do 21 | result = ledger.record(expense) 22 | 23 | expect(result).to be_success 24 | expect(DB[:expenses].all).to match [a_hash_including( 25 | id: result.expense_id, 26 | payee: 'Starbucks', 27 | amount: 5.75, 28 | date: Date.iso8601('2017-06-10') 29 | )] 30 | end 31 | end 32 | 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /06-integration-specs/01/sessions/bundle_install.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle install 5 | Fetching gem metadata from https://rubygems.org/......... 6 | Fetching version metadata from https://rubygems.org/.. 7 | Resolving dependencies... 8 | Using bundler 1.15.3 9 | Using coderay 1.1.1 10 | Using diff-lcs 1.3 11 | Using mustermann 1.0.0 12 | Using rack 2.0.3 13 | Using rspec-support 3.6.0 14 | Fetching sequel 4.48.0 15 | Installing sequel 4.48.0 16 | Using tilt 2.0.7 17 | Fetching sqlite3 1.3.13 18 | Installing sqlite3 1.3.13 with native extensions 19 | Using rack-protection 2.0.0 20 | Using rack-test 0.7.0 21 | Using rspec-core 3.6.0 22 | Using rspec-expectations 3.6.0 23 | Using rspec-mocks 3.6.0 24 | Using sinatra 2.0.0 25 | Using rspec 3.6.0 26 | Bundle complete! 6 Gemfile dependencies, 16 gems now installed. 27 | Use `bundle info [gemname]` to see where a bundled gem is installed. 28 | 29 | -------------------------------------------------------------------------------- /05-unit-specs/12/sessions/rspec_runs/3.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec spec/unit/app/api_spec.rb 5 | 6 | Randomized with seed 27973 7 | 8 | ExpenseTracker::API 9 | POST /expenses 10 | when the expense fails validation 11 |  responds with a 422 (Unprocessable entity) 12 |  returns an error message 13 | when the expense is successfully recorded 14 |  returns the expense id 15 |  responds with a 200 (OK) 16 | GET /expenses/:date 17 | when expenses exist on the given date 18 |  responds with a 200 (OK) 19 |  returns the expense records as JSON 20 | when there are no expenses on the given date 21 |  returns an empty array as JSON 22 |  responds with a 200 (OK) 23 | 24 | Finished in 0.02951 seconds (files took 0.15204 seconds to load) 25 | 8 examples, 0 failures 26 | 27 | Randomized with seed 27973 28 | 29 | -------------------------------------------------------------------------------- /06-integration-specs/03/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec spec/integration/app/ledger_spec.rb 5 | truncated 6 | 7 | Failures: 8 | 9 | 1) ExpenseTracker::Ledger#record with a valid expense successfully saves ↩ 10 | the expense in the DB 11 | Failure/Error: expect(result).to be_success 12 |  expected nil to respond to `success?` 13 | # ./spec/integration/app/ledger_spec.rb:23:in `block (4 levels) in ↩ 14 | ' 15 | 16 | Finished in 0.02211 seconds (files took 0.15418 seconds to load) 17 | 1 example, 1 failure 18 | 19 | Failed examples: 20 | 21 | rspec ./spec/integration/app/ledger_spec.rb:20 # ↩ 22 | ExpenseTracker::Ledger#record with a valid expense successfully saves the ↩ 23 | expense in the DB 24 | 25 | Randomized with seed 27984 26 | 27 | -------------------------------------------------------------------------------- /09-configuring-rspec/05/rspec_configure.rb: -------------------------------------------------------------------------------- 1 | # validate me 2 | require 'rspec/core' 3 | require 'stringio' 4 | 5 | $spec_out = StringIO.new 6 | $spec_err = StringIO.new 7 | RSpec.configuration.output_stream = $spec_out 8 | RSpec.configuration.error_stream = $spec_err 9 | 10 | class SwissArmyKnife; end 11 | 12 | # Old syntax 13 | 14 | describe SwissArmyKnife do # bare `describe` method 15 | it 'is useful' do 16 | knife.should be_useful # `should` expectation 17 | end 18 | end 19 | 20 | RSpec.configure do |config| 21 | config.disable_monkey_patching! 22 | end 23 | 24 | # New syntax 25 | 26 | RSpec.describe SwissArmyKnife do # `describe` called on the `RSpec` module 27 | it 'is useful' do 28 | expect(knife).to be_useful # `expect()`-style expectation 29 | end 30 | end 31 | 32 | RSpec.configure do |config| 33 | config.order = :random 34 | end 35 | 36 | RSpec.configure do |config| 37 | config.add_setting :spec_history_api_key 38 | end 39 | 40 | RSpec.configure do |config| 41 | config.spec_history_api_key = 'a762bc901fga4b185b' 42 | end 43 | -------------------------------------------------------------------------------- /06-integration-specs/04/expense_tracker/spec/integration/app/ledger_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../app/ledger' 2 | require_relative '../../../config/sequel' 3 | require_relative '../../support/db' 4 | 5 | module ExpenseTracker 6 | RSpec.describe Ledger do 7 | let(:ledger) { Ledger.new } 8 | let(:expense) do 9 | { 10 | 'payee' => 'Starbucks', 11 | 'amount' => 5.75, 12 | 'date' => '2017-06-10' 13 | } 14 | end 15 | 16 | describe '#record' do 17 | # ... contexts go here ... 18 | 19 | context 'with a valid expense' do 20 | it 'successfully saves the expense in the DB', :aggregate_failures do 21 | result = ledger.record(expense) 22 | 23 | expect(result).to be_success 24 | expect(DB[:expenses].all).to match [a_hash_including( 25 | id: result.expense_id, 26 | payee: 'Starbucks', 27 | amount: 5.75, 28 | date: Date.iso8601('2017-06-10') 29 | )] 30 | end 31 | end 32 | 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /06-integration-specs/05/expense_tracker/spec/integration/app/ledger_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../app/ledger' 2 | require_relative '../../../config/sequel' 3 | require_relative '../../support/db' 4 | 5 | module ExpenseTracker 6 | RSpec.describe Ledger, :aggregate_failures do 7 | let(:ledger) { Ledger.new } 8 | let(:expense) do 9 | { 10 | 'payee' => 'Starbucks', 11 | 'amount' => 5.75, 12 | 'date' => '2017-06-10' 13 | } 14 | end 15 | 16 | describe '#record' do 17 | # ... contexts go here ... 18 | 19 | context 'with a valid expense' do 20 | it 'successfully saves the expense in the DB' do 21 | result = ledger.record(expense) 22 | 23 | expect(result).to be_success 24 | expect(DB[:expenses].all).to match [a_hash_including( 25 | id: result.expense_id, 26 | payee: 'Starbucks', 27 | amount: 5.75, 28 | date: Date.iso8601('2017-06-10') 29 | )] 30 | end 31 | end 32 | 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /02-running-specs/11/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec 5 | ..FF 6 | 7 | Failures: 8 | 9 | 1) A cup of coffee with milk is light in color FIXED 10 | Expected pending 'Color not implemented yet' to fail. No error was ↩ 11 | raised. 12 | # ./spec/coffee_spec.rb:42 13 | 14 | 2) A cup of coffee with milk is cooler than 200 degrees Fahrenheit FIXED 15 | Expected pending 'Temperature not implemented yet' to fail. No error ↩ 16 | was raised. 17 | # ./spec/coffee_spec.rb:47 18 | 19 | Finished in 0.00293 seconds (files took 0.08214 seconds to load) 20 | 4 examples, 2 failures 21 | 22 | Failed examples: 23 | 24 | rspec ./spec/coffee_spec.rb:42 # A cup of coffee with milk is light in color 25 | rspec ./spec/coffee_spec.rb:47 # A cup of coffee with milk is cooler than ↩ 26 | 200 degrees Fahrenheit 27 | 28 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/05/custom_matchers.irbr: -------------------------------------------------------------------------------- 1 | >> require 'rspec/expectations' 2 | => true 3 | >> include RSpec::Matchers 4 | => Object 5 | 6 | >> RSpec::Matchers.alias_matcher :an_admin, :be_an_admin 7 | => :an_admin 8 | 9 | >> be_an_admin.description 10 | => "be an admin" 11 | >> an_admin.description 12 | => "an admin" 13 | 14 | >> RSpec::Matchers.alias_matcher :an_admin, :be_an_admin do |old_description| 15 | >> old_description.sub('be an admin', 'a superuser') 16 | >> end 17 | => :an_admin 18 | 19 | >> an_admin.description 20 | => "a superuser" 21 | 22 | >> adverb = 'absolutely' 23 | => "absolutely" 24 | 25 | >> expect(adverb).not_to start_with('a').and end_with('z') 26 | NotImplementedError: `expect(...).not_to matcher.and matcher` is not ↩ 27 | supported, since it creates a bit of an ambiguity. Instead, define negated ↩ 28 | versions of whatever matchers you wish to negate with ↩ 29 | `RSpec::Matchers.define_negated_matcher` and use `expect(...).to ↩ 30 | matcher.and matcher`. 31 | backtrace truncated 32 | -------------------------------------------------------------------------------- /02-running-specs/10/spec/coffee_spec.rb: -------------------------------------------------------------------------------- 1 | class Coffee 2 | def ingredients 3 | @ingredients ||= [] 4 | end 5 | 6 | def add(ingredient) 7 | ingredients << ingredient 8 | end 9 | 10 | def price 11 | 1.00 + ingredients.size * 0.25 12 | end 13 | end 14 | 15 | RSpec.configure do |config| 16 | config.filter_run_when_matching(focus: true) 17 | config.example_status_persistence_file_path = 'spec/examples.txt' 18 | end 19 | 20 | RSpec.describe 'A cup of coffee' do 21 | let(:coffee) { Coffee.new } 22 | 23 | it 'costs $1' do 24 | expect(coffee.price).to eq(1.00) 25 | end 26 | 27 | context 'with milk' do 28 | before { coffee.add :milk } 29 | 30 | it 'costs $1.25' do 31 | expect(coffee.price).to eq(1.25) 32 | end 33 | 34 | it 'is light in color' do 35 | pending 'Color not implemented yet' 36 | expect(coffee.color).to be(:light) 37 | end 38 | 39 | it 'is cooler than 200 degrees Fahrenheit' do 40 | pending 'Temperature not implemented yet' 41 | expect(coffee.temperature).to be < 200.0 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/08/custom_matcher/spec/as_of_account_spec.rb: -------------------------------------------------------------------------------- 1 | require 'account' 2 | require 'support/matchers' 3 | 4 | RSpec.describe '`have_a_balance_of(amount)`' do 5 | let(:account) { Account.new('Checking') } 6 | 7 | before do 8 | account.expenses << Expense.new(Date.new(2017, 6, 10), 10) 9 | account.expenses << Expense.new(Date.new(2017, 6, 15), 20) 10 | end 11 | 12 | it 'passes when the balances match' do 13 | expect(account).to have_a_balance_of(30) 14 | # or 15 | expect(account).to have_a_balance_of(10).as_of(Date.new(2017, 6, 12)) 16 | end 17 | 18 | it "fails when a date's balance does not match" do 19 | expect(account).to have_a_balance_of(15).as_of(Date.new(2017, 6, 12)) 20 | end 21 | 22 | it 'can be used in a compound expression' do 23 | expect(account).to have_a_balance_of(30).and \ 24 | have_attributes(name: 'Checking') 25 | end 26 | 27 | it 'can be passed to another matcher' do 28 | user_accounts = [account] 29 | 30 | expect(user_accounts).to include(an_account_with_a_balance_of(30)) 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /06-integration-specs/07/sessions/rspec_runs/5.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec --bisect --seed 32043 5 | Bisect started using options: "--seed 32043" 6 | Running suite to find failures... (0.45293 seconds) 7 | Starting bisect with 2 failing examples and 9 non-failing examples. 8 | Checking that failure(s) are order-dependent... failure appears to be ↩ 9 | order-dependent 10 | 11 | Round 1: bisecting over non-failing examples 1-9 . ignoring examples 1-5 ↩ 12 | (0.45132 seconds) 13 | Round 2: bisecting over non-failing examples 6-9 . ignoring examples 6-7 ↩ 14 | (0.43739 seconds) 15 | Round 3: bisecting over non-failing examples 8-9 . ignoring example 8 ↩ 16 | (0.43102 seconds) 17 | Bisect complete! Reduced necessary non-failing examples from 9 to 1 in 1.64 ↩ 18 | seconds. 19 | 20 | The minimal reproduction command is: 21 | rspec './spec/acceptance/expense_tracker_api_spec.rb[1:1]' ↩ 22 | './spec/integration/app/ledger_spec.rb[1:1:1:1,1:1:2:1]' --seed 32043 23 | 24 | -------------------------------------------------------------------------------- /07-structuring-code-examples/06/transit/spec/berlin_transit_ticket_spec.rb: -------------------------------------------------------------------------------- 1 | require 'berlin_transit_ticket' 2 | 3 | # We disable line length because the labels cause lines to be too long 4 | # but they do not render in the book so it causes no problems. 5 | RSpec.describe BerlinTransitTicket do 6 | let(:ticket) { BerlinTransitTicket.new } 7 | 8 | before do 9 | # These values depend on `let` definitions 10 | # defined in the nested contexts below! 11 | # 12 | ticket.starting_station = starting_station 13 | ticket.ending_station = ending_station 14 | end 15 | 16 | let(:fare) { ticket.fare } 17 | 18 | context 'when starting in zone A' do 19 | let(:starting_station) { 'Bundestag' } 20 | 21 | context 'and ending in zone B' do 22 | let(:ending_station) { 'Leopoldplatz' } 23 | 24 | it 'costs €2.70' do 25 | expect(fare).to eq 2.7 26 | end 27 | end 28 | 29 | context 'and ending in zone C' do 30 | let(:ending_station) { 'Birkenwerder' } 31 | 32 | it 'costs €3.30' do 33 | expect(fare).to eq 3.3 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/09/custom_matcher/spec/support/matchers.rb: -------------------------------------------------------------------------------- 1 | # We disable line length because the labels cause lines to be too long 2 | # but they do not render in the book so it causes no problems. 3 | RSpec::Matchers.define :have_a_balance_of do |amount| 4 | chain(:as_of) { |date| @as_of_date = date } 5 | match do |account| 6 | values_match?(amount, account_balance(account)) 7 | 8 | account_balance(account) == account 9 | end 10 | 11 | match { |account| values_match?(amount, account_balance(account)) } 12 | failure_message { |account| super() + failure_reason(account) } 13 | failure_message_when_negated { |account| super() + failure_reason(account) } 14 | 15 | private 16 | 17 | def failure_reason(account) 18 | ", but had a balance of #{account_balance(account)}" 19 | end 20 | 21 | def account_balance(account) 22 | if @as_of_date 23 | account.balance_as_of(@as_of_date) 24 | else 25 | account.current_balance 26 | end 27 | end 28 | end 29 | 30 | RSpec::Matchers.alias_matcher :an_account_with_a_balance_of, 31 | :have_a_balance_of 32 | -------------------------------------------------------------------------------- /15-using-test-doubles-effectively/solutions/guessing_game/lib/guessing_game.rb: -------------------------------------------------------------------------------- 1 | class GuessingGame 2 | def initialize(input: $stdin, output: $stdout, random: Random.new) 3 | @input, @output, @random = input, output, random 4 | @number = random.rand(1..100) 5 | @guess = nil 6 | end 7 | 8 | def play 9 | 5.downto(1) do |remaining_guesses| 10 | break if @guess == @number 11 | @output.puts "Pick a number 1-100 (#{remaining_guesses} guesses left):" 12 | @guess = @input.gets.to_i 13 | check_guess 14 | end 15 | 16 | announce_result 17 | end 18 | 19 | private 20 | 21 | def check_guess 22 | if @guess > @number 23 | @output.puts "#{@guess} is too high!" 24 | elsif @guess < @number 25 | @output.puts "#{@guess} is too low!" 26 | end 27 | end 28 | 29 | def announce_result 30 | if @guess == @number 31 | @output.puts 'You won!' 32 | else 33 | @output.puts "You lost! The number was: #{@number}" 34 | end 35 | end 36 | end 37 | 38 | # play the game if this file is run directly 39 | GuessingGame.new.play if __FILE__.end_with?($PROGRAM_NAME) 40 | -------------------------------------------------------------------------------- /05-unit-specs/03/expense_tracker/api_snippets.rb: -------------------------------------------------------------------------------- 1 | # Load the spec so we get access to `RecordResult` struct. 2 | require 'rspec/core' 3 | require_relative 'spec/unit/app/api_spec.rb' 4 | require 'sinatra/base' 5 | 6 | class Ledger 7 | def record(_expense) 8 | ExpenseTracker::RecordResult.new 9 | end 10 | end 11 | 12 | class API < Sinatra::Base 13 | def initialize 14 | @ledger = Ledger.new 15 | super() # rest of initialization from Sinatra 16 | end 17 | end 18 | 19 | # Later, callers do this: 20 | app = API.new 21 | app.to_s # avoid unused variable warning 22 | 23 | Object.send(:remove_const, :API) 24 | 25 | class API < Sinatra::Base 26 | def initialize(ledger:) 27 | @ledger = ledger 28 | super() 29 | end 30 | end 31 | 32 | # Later, callers do this: 33 | app = API.new(ledger: Ledger.new) 34 | app.to_s # avoid unused variable warning 35 | 36 | @ledger = Ledger.new 37 | 38 | # Pseudocode for what happens inside the API class: 39 | # 40 | result = @ledger.record({ 'some' => 'data' }) 41 | result.success? # => a Boolean 42 | result.expense_id # => a number 43 | result.error_message # => a string or nil 44 | -------------------------------------------------------------------------------- /05-unit-specs/04/sessions/rspec_runs/2.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec spec/unit/app/api_spec.rb 5 | truncated 6 | 7 | Failures: 8 | 9 | 1) ExpenseTracker::API POST /expenses when the expense is successfully ↩ 10 | recorded responds with a 200 (OK) 11 | Failure/Error: expect(last_response.status).to eq(200) 12 |  13 |  expected: 200 14 |  got: 404 15 |  16 |  (compared using ==) 17 | # ./spec/unit/app/api_spec.rb:41:in `block (4 levels) in ↩ 18 | ' 19 | 20 | Finished in 0.03479 seconds (files took 0.14115 seconds to load) 21 | 4 examples, 1 failure, 2 pending 22 | 23 | Failed examples: 24 | 25 | rspec ./spec/unit/app/api_spec.rb:33 # ExpenseTracker::API POST /expenses ↩ 26 | when the expense is successfully recorded responds with a 200 (OK) 27 | 28 | Randomized with seed 32399 29 | 30 | -------------------------------------------------------------------------------- /14-customizing-test-doubles/02/stock_ticker.rb: -------------------------------------------------------------------------------- 1 | # validate me 2 | require 'timeout' 3 | 4 | class StockTicker 5 | attr_reader :nasdaq_client 6 | 7 | def initialize(nasdaq_client) 8 | @nasdaq_client = nasdaq_client 9 | @error_count = 0 10 | end 11 | 12 | def price(stock_name) 13 | return nil if @error_count >= 3 14 | nasdaq_client.current_price(stock_name) 15 | rescue Timeout::Error 16 | @error_count += 1 17 | nil 18 | end 19 | 20 | def inspect 21 | '#' 22 | end 23 | end 24 | 25 | require 'rspec/mocks/standalone' 26 | 27 | client = instance_double('NasdaqClient') 28 | expect(client).to receive(:current_price).thrice.and_raise(Timeout::Error) 29 | stock_ticker = StockTicker.new(client) 30 | 100.times { stock_ticker.price('AAPL') } 31 | 32 | RSpec::Mocks.verify 33 | 34 | expect(client).to receive(:current_price).exactly(4).times 35 | 36 | expect(client).to receive(:current_price).at_least(3).times 37 | expect(client).to receive(:current_price).at_most(10).times 38 | 39 | expect(client).to receive(:current_price).at_least(:once) 40 | expect(client).to receive(:current_price).at_most(:thrice) 41 | -------------------------------------------------------------------------------- /07-structuring-code-examples/03/expense_tracker/spec/api_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../app/api' 2 | require 'rack/test' 3 | 4 | # This spec file contains a condensed form of `spec/unit/api_spec.rb` for the 5 | # purpose of showing the constructs to share setup logic in ch 7. 6 | 7 | module ExpenseTracker 8 | RSpec.describe 'POST a successful expense' do 9 | # let definitions 10 | let(:ledger) { instance_double('ExpenseTracker::Ledger') } 11 | let(:expense) { { 'some' => 'data' } } 12 | 13 | # hook 14 | before do 15 | allow(ledger).to receive(:record) 16 | .with(expense) 17 | .and_return(RecordResult.new(true, 417, nil)) 18 | end 19 | 20 | # helper method 21 | def parsed_last_response 22 | JSON.parse(last_response.body) 23 | end 24 | 25 | include Rack::Test::Methods 26 | 27 | def app 28 | API.new(ledger: ledger) 29 | end 30 | 31 | it 'responds with a 200 (OK)' do 32 | post '/expenses', JSON.generate(expense) 33 | expect(last_response.status).to eq(200) 34 | expect(parsed_last_response).to include('expense_id' => 417) 35 | end 36 | 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /09-configuring-rspec/02/configuring_rspec/rspec/print_failures_eagerly.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/core' 2 | require 'rspec/core/formatters/base_text_formatter' 3 | require 'rspec/print_failures_eagerly/version' 4 | 5 | # We disable line length because the labels cause lines to be too long 6 | # but they do not render in the book so it causes no problems. 7 | module RSpec 8 | module PrintFailuresEagerly 9 | class Formatter 10 | RSpec::Core::Formatters.register self, :example_failed 11 | 12 | def initialize(output) 13 | @output = output 14 | @last_failure_index = 0 15 | end 16 | 17 | def example_failed(notification) 18 | @output.puts 19 | @output.puts notification.fully_formatted(@last_failure_index += 1) 20 | @output.puts 21 | end 22 | end 23 | module SilenceDumpFailures 24 | def dump_failures(_notification) 25 | end 26 | 27 | RSpec::Core::Formatters::BaseTextFormatter.prepend(self) 28 | end 29 | end 30 | end 31 | 32 | RSpec.configure do |config| 33 | config.before(:suite) do 34 | config.add_formatter RSpec::PrintFailuresEagerly::Formatter 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /11-matchers-included-in-rspec-expectations/exercises/sessions/water_spec.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec water_spec.rb 5 | F 6 | 7 | Failures: 8 | 9 | 1) Water is H2O 10 | Failure/Error: expect(Water.elements.sort).to eq [:hydrogen, ↩ 11 | :hydrogen, :oxygen] 12 |  13 |  expected: [:hydrogen, :hydrogen, :oxygen] 14 |  got: [:hydrogen, :oxygen] 15 |  16 |  (compared using ==) 17 |  18 |  Diff: 19 |  @@ -1,2 +1,2 @@ 20 |  -[:hydrogen, :hydrogen, :oxygen] 21 |  +[:hydrogen, :oxygen] 22 |   23 | # ./water_spec.rb:9:in `block (2 levels) in ' 24 | 25 | Finished in 0.01163 seconds (files took 0.08126 seconds to load) 26 | 1 example, 1 failure 27 | 28 | Failed examples: 29 | 30 | rspec ./water_spec.rb:8 # Water is H2O 31 | 32 | -------------------------------------------------------------------------------- /10-exploring-rspec-expectations/04/composing_matchers.rb: -------------------------------------------------------------------------------- 1 | # validate me 2 | require 'rspec/expectations' 3 | include RSpec::Matchers 4 | 5 | presidents = [ 6 | { name: 'George Washington', birth_year: 1732 }, 7 | { name: 'John Adams', birth_year: 1735 }, 8 | { name: 'Thomas Jefferson', birth_year: 1743 }, 9 | # ... 10 | ] 11 | expect(presidents).to start_with( 12 | { name: 'George Washington', birth_year: a_value_between(1730, 1740) }, 13 | { name: 'John Adams', birth_year: a_value_between(1730, 1740) } 14 | ) 15 | 16 | alphabet = ('a'..'z').to_a 17 | expect(alphabet).to start_with('a').and end_with('z') 18 | 19 | stoplight_color = %w[ green red yellow ].sample 20 | expect(stoplight_color).to eq('green').or eq('red').or eq('yellow') 21 | 22 | alphabet = ('a'..'z').to_a 23 | expect(alphabet).to start_with('a') & end_with('z') 24 | 25 | stoplight_color = %w[ green red yellow ].sample 26 | expect(stoplight_color).to eq('green') | eq('red') | eq('yellow') 27 | 28 | letter_ranges = ['N to Z', 'A to M'] 29 | expect(letter_ranges).to contain_exactly( 30 | a_string_starting_with('A') & ending_with('M'), 31 | a_string_starting_with('N') & ending_with('Z') 32 | ) 33 | -------------------------------------------------------------------------------- /04-acceptance-specs/01/sessions/bundle_install.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle install 5 | Fetching gem metadata from https://rubygems.org/......... 6 | Fetching version metadata from https://rubygems.org/.. 7 | Resolving dependencies... 8 | Using bundler 1.15.3 9 | Using coderay 1.1.1 10 | Using diff-lcs 1.3 11 | Fetching mustermann 1.0.0 12 | Installing mustermann 1.0.0 13 | Fetching rack 2.0.3 14 | Installing rack 2.0.3 15 | Using rspec-support 3.6.0 16 | Fetching tilt 2.0.7 17 | Installing tilt 2.0.7 18 | Fetching rack-protection 2.0.0 19 | Installing rack-protection 2.0.0 20 | Fetching rack-test 0.7.0 21 | Installing rack-test 0.7.0 22 | Using rspec-core 3.6.0 23 | Using rspec-expectations 3.6.0 24 | Using rspec-mocks 3.6.0 25 | Fetching sinatra 2.0.0 26 | Installing sinatra 2.0.0 27 | Using rspec 3.6.0 28 | Bundle complete! 4 Gemfile dependencies, 14 gems now installed. 29 | Use `bundle info [gemname]` to see where a bundled gem is installed. 30 | 31 | -------------------------------------------------------------------------------- /06-integration-specs/07/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec spec/integration/app/ledger_spec.rb 5 | truncated 6 | 7 | Failures: 8 | 9 | 1) ExpenseTracker::Ledger#record when the expense lacks a payee rejects ↩ 10 | the expense as invalid 11 | 12 | truncated 13 | 14 | 2) ExpenseTracker::Ledger#record with a valid expense successfully saves ↩ 15 | the expense in the DB 16 | 17 | truncated 18 | 19 | Finished in 0.02597 seconds (files took 0.18213 seconds to load) 20 | 2 examples, 2 failures 21 | 22 | Failed examples: 23 | 24 | rspec ./spec/integration/app/ledger_spec.rb:34 # ↩ 25 | ExpenseTracker::Ledger#record when the expense lacks a payee rejects the ↩ 26 | expense as invalid 27 | rspec ./spec/integration/app/ledger_spec.rb:20 # ↩ 28 | ExpenseTracker::Ledger#record with a valid expense successfully saves the ↩ 29 | expense in the DB 30 | 31 | Randomized with seed 57045 32 | 33 | -------------------------------------------------------------------------------- /11-matchers-included-in-rspec-expectations/solutions/sessions/tokenizer_spec.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec tokenizer_spec.rb 5 | F 6 | 7 | Failures: 8 | 9 | 1) Tokenizer tokenizes multiple lines of text 10 | Failure/Error: expect(tokenized).to start_with('I', 'am', 'Sam.', ↩ 11 | 'Sam', 'I', 'am') 12 |  expected ["", "I", "am", "Sam.\n", "Sam", "I", "am.\n", "Do", "you", ↩ 13 | "like", "green", "eggs", "and", "ham?\n"] to start with "I", "am", ↩ 14 | "Sam.", "Sam", "I", and "am" 15 | # ./tokenizer_spec.rb:18:in `block (2 levels) in ' 16 | 17 | Finished in 0.01074 seconds (files took 0.07581 seconds to load) 18 | 1 example, 1 failure 19 | 20 | Failed examples: 21 | 22 | rspec ./tokenizer_spec.rb:16 # Tokenizer tokenizes multiple lines of text 23 | 24 | -------------------------------------------------------------------------------- /08-metadata/01/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec spec/metadata_spec.rb 5 | {:block=> 6 | #, 7 | :description_args=>["is used by RSpec for metadata"], 8 | :description=>"is used by RSpec for metadata", 9 | :full_description=>"Hash is used by RSpec for metadata", 10 | :described_class=>Hash, 11 | :file_path=>"./spec/metadata_spec.rb", 12 | :line_number=>4, 13 | :location=>"./spec/metadata_spec.rb:4", 14 | :absolute_file_path=> 15 | "~/code/metadata/spec/metadata_spec.rb", 16 | :rerun_file_path=>"./spec/metadata_spec.rb", 17 | :scoped_id=>"1:1", 18 | :execution_result=> 19 | #, 21 | :example_group=> 22 | {:block=> 23 | #, 24 | 25 | truncated 26 | 27 | :shared_group_inclusion_backtrace=>[], 28 | :last_run_status=>"unknown"} 29 | . 30 | 31 | Finished in 0.00279 seconds (files took 0.09431 seconds to load) 32 | 1 example, 0 failures 33 | 34 | -------------------------------------------------------------------------------- /02-running-specs/11/spec/coffee_spec.rb: -------------------------------------------------------------------------------- 1 | class Coffee 2 | def ingredients 3 | @ingredients ||= [] 4 | end 5 | 6 | def add(ingredient) 7 | ingredients << ingredient 8 | end 9 | 10 | def price 11 | 1.00 + ingredients.size * 0.25 12 | end 13 | 14 | def color 15 | ingredients.include?(:milk) ? :light : :dark 16 | end 17 | 18 | def temperature 19 | ingredients.include?(:milk) ? 190.0 : 205.0 20 | end 21 | end 22 | 23 | RSpec.configure do |config| 24 | config.filter_run_when_matching(focus: true) 25 | config.example_status_persistence_file_path = 'spec/examples.txt' 26 | end 27 | 28 | RSpec.describe 'A cup of coffee' do 29 | let(:coffee) { Coffee.new } 30 | 31 | it 'costs $1' do 32 | expect(coffee.price).to eq(1.00) 33 | end 34 | 35 | context 'with milk' do 36 | before { coffee.add :milk } 37 | 38 | it 'costs $1.25' do 39 | expect(coffee.price).to eq(1.25) 40 | end 41 | 42 | it 'is light in color' do 43 | pending 'Color not implemented yet' 44 | expect(coffee.color).to be(:light) 45 | end 46 | 47 | it 'is cooler than 200 degrees Fahrenheit' do 48 | pending 'Temperature not implemented yet' 49 | expect(coffee.temperature).to be < 200.0 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /06-integration-specs/09/sessions/each_spec_file_success.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ (for f in `find spec -iname '*_spec.rb'`; do ↩ 5 | echo "$f:" ↩ 6 | bundle exec rspec $f -fp || exit 1 ↩ 7 | done) 8 | spec/acceptance/expense_tracker_api_spec.rb: 9 | 10 | Randomized with seed 64689 11 | . 12 | 13 | Finished in 0.02758 seconds (files took 0.20933 seconds to load) 14 | 1 example, 0 failures 15 | 16 | Randomized with seed 64689 17 | 18 | spec/integration/app/ledger_spec.rb: 19 | 20 | Randomized with seed 7247 21 | .... 22 | 23 | Finished in 0.01689 seconds (files took 0.15956 seconds to load) 24 | 4 examples, 0 failures 25 | 26 | Randomized with seed 7247 27 | 28 | spec/unit/app/api_spec.rb: 29 | 30 | Randomized with seed 21495 31 | ........ 32 | 33 | Finished in 0.02619 seconds (files took 0.21264 seconds to load) 34 | 8 examples, 0 failures 35 | 36 | Randomized with seed 21495 37 | 38 | -------------------------------------------------------------------------------- /12-creating-custom-matchers/exercises/custom_matcher/spec/event_matchers_spec.rb: -------------------------------------------------------------------------------- 1 | Event = Struct.new(:name, :capacity) do 2 | def purchase_ticket_for(guest) 3 | tickets_sold << guest 4 | end 5 | 6 | def tickets_sold 7 | @tickets_sold ||= [] 8 | end 9 | 10 | def inspect 11 | "#" 12 | end 13 | end 14 | 15 | RSpec.describe '`have_no_tickets_sold` matcher' do 16 | example 'passing expectation' do 17 | art_show = Event.new('Art Show', 100) 18 | 19 | expect(art_show).to have_no_tickets_sold 20 | end 21 | 22 | example 'failing expectation' do 23 | art_show = Event.new('Art Show', 100) 24 | art_show.purchase_ticket_for(:a_friend) 25 | 26 | expect(art_show).to have_no_tickets_sold 27 | end 28 | end 29 | 30 | RSpec.describe '`be_sold_out` matcher' do 31 | example 'passing expectation' do 32 | u2_concert = Event.new('U2 Concert', 10_000) 33 | 10_000.times { u2_concert.purchase_ticket_for(:a_fan) } 34 | 35 | expect(u2_concert).to be_sold_out 36 | end 37 | 38 | example 'failing expectation' do 39 | u2_concert = Event.new('U2 Concert', 10_000) 40 | 9_900.times { u2_concert.purchase_ticket_for(:a_fan) } 41 | 42 | expect(u2_concert).to be_sold_out 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /06-integration-specs/07/sessions/rspec_runs/6.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec './spec/acceptance/expense_tracker_api_spec.rb[1:1]' ↩ 5 | './spec/integration/app/ledger_spec.rb[1:1:1:1,1:1:2:1]' --seed 32043 6 | Run options: include ↩ 7 | {:ids=>{"./spec/acceptance/expense_tracker_api_spec.rb"=>["1:1"], ↩ 8 | "./spec/integration/app/ledger_spec.rb"=>["1:1:1:1", "1:1:2:1"]}} 9 | 10 | Randomized with seed 32043 11 | *FF 12 | 13 | truncated 14 | 15 | Finished in 0.0485 seconds (files took 0.21859 seconds to load) 16 | 3 examples, 2 failures, 1 pending 17 | 18 | Failed examples: 19 | 20 | rspec ./spec/integration/app/ledger_spec.rb:20 # ↩ 21 | ExpenseTracker::Ledger#record with a valid expense successfully saves the ↩ 22 | expense in the DB 23 | rspec ./spec/integration/app/ledger_spec.rb:34 # ↩ 24 | ExpenseTracker::Ledger#record when the expense lacks a payee rejects the ↩ 25 | expense as invalid 26 | 27 | Randomized with seed 32043 28 | 29 | -------------------------------------------------------------------------------- /04-acceptance-specs/06/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec 5 | * 6 | 7 | Pending: (Failures listed here are expected and do not affect your suite's ↩ 8 | status) 9 | 10 | 1) Expense Tracker API records submitted expenses 11 | # Need to persist expenses 12 | Failure/Error: expect(expenses).to contain_exactly(coffee, zoo) 13 |  14 |  expected collection contained: [{"payee"=>"Starbucks", ↩ 15 | "amount"=>5.75, "date"=>"2017-06-10", "id"=>42}, {"payee"=>"Zoo", ↩ 16 | "amount"=>15.25, "date"=>"2017-06-10", "id"=>42}] 17 |  actual collection contained: [] 18 |  the missing elements were: [{"payee"=>"Starbucks", ↩ 19 | "amount"=>5.75, "date"=>"2017-06-10", "id"=>42}, {"payee"=>"Zoo", ↩ 20 | "amount"=>15.25, "date"=>"2017-06-10", "id"=>42}] 21 | # ./spec/acceptance/expense_tracker_api_spec.rb:46:in `block (2 ↩ 22 | levels) in ' 23 | 24 | Finished in 0.03437 seconds (files took 0.1271 seconds to load) 25 | 1 example, 0 failures, 1 pending 26 | 27 | -------------------------------------------------------------------------------- /05-unit-specs/03/expense_tracker/spec/unit/app/api_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../app/api' 2 | require 'rack/test' 3 | 4 | module ExpenseTracker 5 | RecordResult = Struct.new(:success?, :expense_id, :error_message) 6 | 7 | RSpec.describe API do 8 | include Rack::Test::Methods 9 | 10 | def app 11 | API.new(ledger: ledger) 12 | end 13 | 14 | let(:ledger) { instance_double('ExpenseTracker::Ledger') } 15 | 16 | describe 'POST /expenses' do 17 | context 'when the expense is successfully recorded' do 18 | # ... specs go here ... 19 | 20 | it 'returns the expense id' do 21 | expense = { 'some' => 'data' } 22 | 23 | allow(ledger).to receive(:record) 24 | .with(expense) 25 | .and_return(RecordResult.new(true, 417, nil)) 26 | 27 | post '/expenses', JSON.generate(expense) 28 | 29 | parsed = JSON.parse(last_response.body) 30 | expect(parsed).to include('expense_id' => 417) 31 | end 32 | 33 | it 'responds with a 200 (OK)' 34 | end 35 | 36 | context 'when the expense fails validation' do 37 | # ... specs go here ... 38 | it 'returns an error message' 39 | it 'responds with a 422 (Unprocessable entity)' 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /11-matchers-included-in-rspec-expectations/exercises/sessions/phone_number_extractor_spec.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec phone_number_extractor_spec.rb 5 | F 6 | 7 | Failures: 8 | 9 | 1) PhoneNumberExtractor yields phone numbers as it finds them 10 | Failure/Error: 11 |  expect(yielded_numbers).to eq [ 12 |  '(202) 555-0168', 13 |  '202-555-0199', 14 |  '(202) 555-0175' 15 |  ] 16 |  17 |  expected: ["(202) 555-0168", "202-555-0199", "(202) 555-0175"] 18 |  got: [] 19 |  20 |  (compared using ==) 21 | # ./phone_number_extractor_spec.rb:23:in `block (2 levels) in ' 23 | 24 | Finished in 0.00956 seconds (files took 0.08245 seconds to load) 25 | 1 example, 1 failure 26 | 27 | Failed examples: 28 | 29 | rspec ./phone_number_extractor_spec.rb:17 # PhoneNumberExtractor yields ↩ 30 | phone numbers as it finds them 31 | 32 | -------------------------------------------------------------------------------- /05-unit-specs/03/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ bundle exec rspec spec/unit/app/api_spec.rb 5 | truncated 6 | 7 | Failures: 8 | 9 | 1) ExpenseTracker::API POST /expenses when the expense is successfully ↩ 10 | recorded returns the expense id 11 | Failure/Error: expect(parsed).to include('expense_id' => 417) 12 |  13 |  expected {"expense_id" => 42} to include {"expense_id" => 417} 14 |  Diff: 15 |  @@ -1,2 +1,2 @@ 16 |  -"expense_id" => 417, 17 |  +"expense_id" => 42, 18 |   19 | # ./spec/unit/app/api_spec.rb:30:in `block (4 levels) in ↩ 20 | ' 21 | 22 | Finished in 0.03784 seconds (files took 0.16365 seconds to load) 23 | 4 examples, 1 failure, 3 pending 24 | 25 | Failed examples: 26 | 27 | rspec ./spec/unit/app/api_spec.rb:20 # ExpenseTracker::API POST /expenses ↩ 28 | when the expense is successfully recorded returns the expense id 29 | 30 | Randomized with seed 56373 31 | 32 | -------------------------------------------------------------------------------- /09-configuring-rspec/02/sessions/rspec_runs/1.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $ rspec a_spec.rb 5 | F 6 | 7 | 1) A group with a failure has an example that fails 8 | Failure/Error: expect(1).to eq 2 9 |  10 |  expected: 2 11 |  got: 1 12 |  13 |  (compared using ==) 14 | # ./a_spec.rb:3:in `block (2 levels) in ' 15 | 16 | .F 17 | 18 | 2) Another group with a failure has an example that fails 19 | Failure/Error: expect(1).to eq 2 20 |  21 |  expected: 2 22 |  got: 1 23 |  24 |  (compared using ==) 25 | # ./a_spec.rb:13:in `block (2 levels) in ' 26 | 27 | . 28 | 29 | Finished in 0.0307 seconds (files took 0.08058 seconds to load) 30 | 4 examples, 2 failures 31 | 32 | Failed examples: 33 | 34 | rspec ./a_spec.rb:2 # A group with a failure has an example that fails 35 | rspec ./a_spec.rb:12 # Another group with a failure has an example that fails 36 | 37 | --------------------------------------------------------------------------------