Instantly share code, notes, and snippets.
Last active
February 28, 2020 07:54
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save AspenJames/f6413f6f2c0e166bb8e5c87328ef3c14 to your computer and use it in GitHub Desktop.
RSpec shared examples for common rails relationships
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This is a way to test boilerplate Rails relationships with a clean syntax in the model spec files: | |
# Relies on factory_bot_rails and factories defined for each model | |
# context "Relationships:" do | |
# before :each do | |
# @coffee = create(:coffee) | |
# end | |
# describe "Roaster:" do | |
# it_behaves_like "a belongs_to relationship" do | |
# let(:described) { @coffee } | |
# let(:relation_name) { :roaster } | |
# end | |
# end | |
# describe "Recipes:" do | |
# it_behaves_like "a has_many relationship" do | |
# let(:described) { @coffee } | |
# let(:relation_name) { :recipes } | |
# end | |
# end | |
# describe "Entries:" do | |
# it_behaves_like "a has_many, through relationship" do | |
# let(:described) { @coffee } | |
# let(:relation_name) { :entries } | |
# let(:through_name) { :recipes } | |
# end | |
# end | |
# end | |
RSpec.shared_examples "a belongs_to relationship" do | |
# described => instance of class provided for testing | |
# relation_name => symbol passed to belongs_to in model file | |
before(:all) do | |
raise DescribedObjectRequired if !respond_to?(:described) | |
raise RelationMethodNameRequired if !respond_to?(:relation_name) | |
end | |
it "responds to #relation_name and returns an instance" do | |
expect(described.respond_to?(relation_name)).to be true | |
expect(described.send(relation_name)).not_to be_a_kind_of(ActiveRecord::Associations::CollectionProxy) | |
end | |
it "belongs to the given relation" do | |
rel = described.send(relation_name) | |
relation_class = relation_name.to_s.capitalize.constantize | |
described_class_sym_plural = described_class.name.downcase.pluralize.to_sym | |
expect(rel).to be_a_kind_of(relation_class) | |
expect(rel.send(described_class_sym_plural)).to include(described) | |
end | |
end | |
RSpec.shared_examples "a has_many relationship" do | |
# described => instance of class provided for testing | |
# relation_name => symbol passed to has_many in model file | |
before(:all) do | |
raise DescribedObjectRequired if !respond_to?(:described) | |
raise RelationMethodNameRequired if !respond_to?(:relation_name) | |
end | |
def relation_class | |
relation_name.to_s.singularize.capitalize.constantize | |
end | |
it "responds to #relation_name and returns a collection" do | |
expect(described.respond_to?(relation_name)).to be true | |
expect(described.send(relation_name)).to be_a_kind_of(ActiveRecord::Associations::CollectionProxy) | |
end | |
it "initially has an empty relation set" do | |
expect(described.send(relation_name).length).to eq 0 | |
end | |
it "can return a single relation" do | |
relation_class_sym = relation_name.to_s.singularize.to_sym | |
described_class_sym = described_class.name.downcase.to_sym | |
opts = {} | |
opts[described_class_sym] = described | |
rel = create(relation_class_sym, opts) | |
related = described.send(relation_name) | |
expect(related.length).to eq 1 | |
expect(related[0]).to eq rel | |
expect(related[0]).to be_a_kind_of(relation_class) | |
expect(related[0].send(described_class_sym)).to eq described | |
end | |
it "can return multiple relations" do | |
relation_class_sym = relation_name.to_s.singularize.to_sym | |
described_class_sym = described_class.name.downcase.to_sym | |
opts = {} | |
opts[described_class_sym] = described | |
5.times { create(relation_class_sym, opts) } | |
related = described.send(relation_name) | |
expect(related.length).to eq 5 | |
related.each do |r| | |
expect(r).to be_a_kind_of(relation_class) | |
expect(r.send(described_class_sym)).to eq described | |
end | |
end | |
end | |
RSpec.shared_examples "a has_many, through relationship" do | |
# described => instance of class provided for testing | |
# relation_name => symbol passed to has_many in model file | |
# through_name => symbol passed to through: in model file | |
before(:all) do | |
raise DescribedObjectRequired if !respond_to?(:described) | |
raise RelationMethodNameRequired if !respond_to?(:relation_name) | |
raise ThroughNameRequired if !respond_to?(:through_name) | |
end | |
def relation_class | |
relation_name.to_s.singularize.capitalize.constantize | |
end | |
def through_class | |
through_name.to_s.singularize.capitalize.constantize | |
end | |
it "responds to #relation_name" do | |
expect(described.respond_to?(relation_name)).to be true | |
expect(described.send(relation_name)).to be_a_kind_of(ActiveRecord::Associations::CollectionProxy) | |
end | |
it "initially has an empty relation set" do | |
expect(described.send(relation_name).length).to eq 0 | |
end | |
it "can return a single relation" do | |
described_class_sym = described_class.name.downcase.to_sym | |
through_sym = through_name.to_s.singularize.to_sym | |
rel_sym = relation_name.to_s.singularize.to_sym | |
through_opt = {} | |
through_opt[described_class_sym] = described | |
rel_opt = {} | |
rel_opt[through_sym] = create(through_sym, through_opt) | |
rel = create(rel_sym, rel_opt) | |
expect(described.send(relation_name).length).to eq 1 | |
expect(described.send(relation_name).first).to eq rel | |
expect(rel.send(described_class_sym)).to eq described | |
end | |
it "can return multiple relations" do | |
described_class_sym = described_class.name.downcase.to_sym | |
through_sym = through_name.to_s.singularize.to_sym | |
rel_sym = relation_name.to_s.singularize.to_sym | |
through_opt = {} | |
through_opt[described_class_sym] = described | |
5.times do | |
rel_opt = {} | |
rel_opt[through_sym] = create(through_sym, through_opt) | |
create(rel_sym, rel_opt) | |
end | |
related = described.send(relation_name) | |
expect(related.length).to eq 5 | |
related.each do |r| | |
expect(r).to be_a_kind_of(relation_class) | |
expect(r.send(described_class_sym)).to eq described | |
end | |
end | |
end | |
class DescribedObjectRequired < StandardError | |
DEFAULT_MSG = <<-TEXT | |
":described" must be defined. | |
=> Please supply the instance with which to test | |
=> ex. class Blog; has_many :posts; end | |
=> let(:described) { Blog.create } | |
TEXT | |
def initialize(msg=DEFAULT_MSG) | |
super(msg) | |
end | |
end | |
class RelationMethodNameRequired < StandardError | |
DEFAULT_MSG = <<-TEXT | |
":relation_name" must be defined. | |
=> Please supply the symbol passed to has_many in the model file | |
=> ex. class Blog; has_many :posts; end | |
=> let(:relation_name) { :posts } | |
TEXT | |
def initialize(msg=DEFAULT_MSG) | |
super(msg) | |
end | |
end | |
class ThroughNameRequired < StandardError | |
DEFAULT_MSG = <<-TEXT | |
":through_name" must be defined. | |
=> Please supply the symbol passed to through: in the model file | |
=> ex. class Blog; has_many :comments, through: :posts; end | |
=> let(:relation_name) { :comments } | |
=> let(:through_name) { :posts } | |
TEXT | |
def initialize(msg=DEFAULT_MSG) | |
super(msg) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment