Shoulda ======= ActiveRecord Matchers --------------------- Matchers to test associations: ```ruby describe Post do it { should belong_to(:user) } it { should have_many(:tags).through(:taggings) } end describe User do it { should have_many(:posts) } end it { should have_and_belong_to_many(:posts) } it { should_not have_db_column(:admin).of_type(:boolean) } it { should have_db_column(:salary). of_type(:decimal). with_options(:precision => 10, :scale => 2) } it { should have_db_column(:id).with_options(:primary => true) } it { should have_db_index(:age) } it { should have_db_index([:commentable_type, :commentable_id]) } it { should have_db_index(:ssn).unique(true) } it { should have_many(:friends) } it { should have_many(:enemies).through(:friends) } it { should have_many(:enemies).dependent(:destroy) } it { should have_one(:god) } it { should have_readonly_attribute(:password) } it { should query_the_database(4.times).when_calling(:complicated_method) } it { should query_the_database(4.times).or_less.when_calling(:complicated_method) } it { should_not query_the_database.when_calling(:complicated_method) } ``` ActiveModel Matchers -------------------- Matchers to test validations and mass assignments: ```ruby describe Post do it { should validate_uniqueness_of(:title) } it { should validate_presence_of(:body).with_message(/wtf/) } it { should validate_presence_of(:title) } it { should validate_numericality_of(:user_id) } it { should validate_numericality_of(:user_id).only_integer } it { should validate_acceptance_of(:eula) } it { should validate_format_of(:name). with('12345'). with_message(/is not optional/) } it { should validate_format_of(:name). not_with('12D45'). with_message(/is not optional/) } it { should validate_numericality_of(:age) } it { should validate_confirmation_of(:password) } # validates_uniqueness_of requires an entry to be in the database already it "validates uniqueness of title" do Post.create!(title: "My Awesome Post", body: "whatever") should validate_uniqueness_of(:title) end end describe User do it { should_not allow_value("blah").for(:email) } it { should allow_value("a@b.com").for(:email) } it { should ensure_inclusion_of(:age).in_range(1..100) } it { should ensure_exclusion_of(:age).in_range(30..60) } it { should_not allow_mass_assignment_of(:password) } end it { should ensure_length_of(:password). is_at_least(6). is_at_most(20) } it { should ensure_length_of(:name). is_at_least(3). with_short_message(/not long enough/) } it { should ensure_length_of(:ssn). is_equal_to(9). with_message(/is invalid/) } ``` ActionController Matchers ------------------------- Matchers to test common patterns: ```ruby describe PostsController, "#show" do context "for a fictional user" do before do get :show, :id => 1 end it { should assign_to(:user) } it { should respond_with(:success) } it { should render_template(:show) } it { should_not set_the_flash } it { should set_the_flash[:alert].to("Password doesn't match") } end end # other examples it { should assign_to(:user) } it { should_not assign_to(:user) } it { should assign_to(:user).with_kind_of(User) } it { should assign_to(:user).with(@user) } it { should render_with_layout } it { should render_with_layout(:special) } it { should_not render_with_layout } it { should respond_with(:success) } it { should respond_with(:redirect) } it { should respond_with(:missing) } it { should respond_with(:error) } it { should respond_with(501) } it { should respond_with_content_type(:xml) } it { should respond_with_content_type(:csv) } it { should respond_with_content_type(:atom) } it { should respond_with_content_type(:yaml) } it { should respond_with_content_type(:text) } it { should respond_with_content_type('application/rss+xml') } it { should respond_with_content_type(/json/) } it { should set_session(:message) } it { should set_session(:user_id).to(@user.id) } it { should_not set_session(:user_id) } it { should set_the_flash } it { should set_the_flash.to("Thank you for placing this order.") } it { should set_the_flash.to(/created/i) } it { should set_the_flash.to(/logged in/i).now } it { should_not set_the_flash } it { should filter_param(:password) } ``` ActionMailer Matchers --------------------- ```ruby it { should have_sent_email.with_subject(/is spam$/) } it { should have_sent_email.from('do-not-reply@example.com') } it { should have_sent_email.with_body(/is spam\./) } it { should have_sent_email.to('myself@me.com') } it { should have_sent_email.with_part('text/html', /HTML spam/) } it { should have_sent_email.with_subject(/spam/). from('do-not-reply@example.com'). with_body(/spam/). to('myself@me.com') } it {should have_sent_email.to {@user.email} } it { should have_sent_email.reply_to([user, other]) } ```