How to stub has_many association in RSpec












1















I'm try to stub has_many association in RSpec because building records is so complicated. I want to detect which author has science book by using Author#has_science_tag?.



Model



class Book < ApplicationRecord
has_and_belongs_to_many :tags
belongs_to :author
end

class Tag < ApplicationRecord
has_and_belongs_to_many :books
end

class Author < ApplicationRecord
has_many :books

def has_science_tag?
tags = books.joins(:tags).pluck('tags.name')
tags.grep(/science/i).present?
end
end


RSpec



require 'rails_helper'

RSpec.describe Author, type: :model do
describe '#has_science_tag?' do
let(:author) { create(:author) }

context 'one science book' do
example 'it returns true' do
allow(author).to receive_message_chain(:books, :joins, :pluck).with(no_args).with(:tags).with('tags.name').and_return(['Science'])
expect(author.has_science_tag?).to be_truthy
end
end
end
end


In this case, using receive_message_chain is good choice? Or stubbing has_many association is bad idea?










share|improve this question



























    1















    I'm try to stub has_many association in RSpec because building records is so complicated. I want to detect which author has science book by using Author#has_science_tag?.



    Model



    class Book < ApplicationRecord
    has_and_belongs_to_many :tags
    belongs_to :author
    end

    class Tag < ApplicationRecord
    has_and_belongs_to_many :books
    end

    class Author < ApplicationRecord
    has_many :books

    def has_science_tag?
    tags = books.joins(:tags).pluck('tags.name')
    tags.grep(/science/i).present?
    end
    end


    RSpec



    require 'rails_helper'

    RSpec.describe Author, type: :model do
    describe '#has_science_tag?' do
    let(:author) { create(:author) }

    context 'one science book' do
    example 'it returns true' do
    allow(author).to receive_message_chain(:books, :joins, :pluck).with(no_args).with(:tags).with('tags.name').and_return(['Science'])
    expect(author.has_science_tag?).to be_truthy
    end
    end
    end
    end


    In this case, using receive_message_chain is good choice? Or stubbing has_many association is bad idea?










    share|improve this question

























      1












      1








      1








      I'm try to stub has_many association in RSpec because building records is so complicated. I want to detect which author has science book by using Author#has_science_tag?.



      Model



      class Book < ApplicationRecord
      has_and_belongs_to_many :tags
      belongs_to :author
      end

      class Tag < ApplicationRecord
      has_and_belongs_to_many :books
      end

      class Author < ApplicationRecord
      has_many :books

      def has_science_tag?
      tags = books.joins(:tags).pluck('tags.name')
      tags.grep(/science/i).present?
      end
      end


      RSpec



      require 'rails_helper'

      RSpec.describe Author, type: :model do
      describe '#has_science_tag?' do
      let(:author) { create(:author) }

      context 'one science book' do
      example 'it returns true' do
      allow(author).to receive_message_chain(:books, :joins, :pluck).with(no_args).with(:tags).with('tags.name').and_return(['Science'])
      expect(author.has_science_tag?).to be_truthy
      end
      end
      end
      end


      In this case, using receive_message_chain is good choice? Or stubbing has_many association is bad idea?










      share|improve this question














      I'm try to stub has_many association in RSpec because building records is so complicated. I want to detect which author has science book by using Author#has_science_tag?.



      Model



      class Book < ApplicationRecord
      has_and_belongs_to_many :tags
      belongs_to :author
      end

      class Tag < ApplicationRecord
      has_and_belongs_to_many :books
      end

      class Author < ApplicationRecord
      has_many :books

      def has_science_tag?
      tags = books.joins(:tags).pluck('tags.name')
      tags.grep(/science/i).present?
      end
      end


      RSpec



      require 'rails_helper'

      RSpec.describe Author, type: :model do
      describe '#has_science_tag?' do
      let(:author) { create(:author) }

      context 'one science book' do
      example 'it returns true' do
      allow(author).to receive_message_chain(:books, :joins, :pluck).with(no_args).with(:tags).with('tags.name').and_return(['Science'])
      expect(author.has_science_tag?).to be_truthy
      end
      end
      end
      end


      In this case, using receive_message_chain is good choice? Or stubbing has_many association is bad idea?







      ruby rspec






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 21 '18 at 12:10









      Akira NoguchiAkira Noguchi

      3341815




      3341815
























          1 Answer
          1






          active

          oldest

          votes


















          2














          Why dont you make use of FactoryBot associations?



          FactoryBot.define do
          # tag factory with a `belongs_to` association for the book
          factory :tag do
          name { 'test_tag' }
          book

          trait :science do
          name { 'science' }
          end
          end

          # book factory with a `belongs_to` association for the author
          factory :book do
          title { "Through the Looking Glass" }
          author

          factory :science_book do
          title { "Some science stuff" }

          after(:create) do |book, evaluator|
          create(:tag, :science, book: book)
          end
          end
          end

          # author factory without associated books
          factory :author do
          name { "John Doe" }

          # author_with_science_books will create book data after the author has
          # been created
          factory :author_with_science_books do
          # books_count is declared as an ignored attribute and available in
          # attributes on the factory, as well as the callback via the evaluator
          transient do
          books_count { 5 }
          end

          # the after(:create) yields two values; the author instance itself and
          # the evaluator, which stores all values from the factory, including
          # ignored attributes; `create_list`'s second argument is the number of
          # records to create and we make sure the author is associated properly
          # to the book
          after(:create) do |author, evaluator|
          create_list(:science_book, evaluator.books_count, authors: [author])
          end
          end
          end
          end


          This allows you to do:



          create(:author).books.count # 0
          create(:author_with_science_books).books.count # 5
          create(:author_with_science_books, books_count: 15).books.count # 15


          So your test becomes:



          RSpec.describe Author, type: :model do
          describe '#has_science_tag?' do
          let(:author_with_science_books) { create(:author_with_science_books, books_count: 1) }

          context 'one science book' do
          it 'returns true' do
          expect(author_with_science_books.has_science_tag?).to eq true
          end
          end
          end
          end




          And you could also refactor Author#has_science_tag?:



          class Author < ApplicationRecord
          has_many :books

          def has_science_tag?
          books.joins(:tags).where("tags.name ILIKE '%science%'").exists?
          end
          end





          share|improve this answer

























            Your Answer






            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "1"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: true,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: 10,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53411752%2fhow-to-stub-has-many-association-in-rspec%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            1 Answer
            1






            active

            oldest

            votes








            1 Answer
            1






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            2














            Why dont you make use of FactoryBot associations?



            FactoryBot.define do
            # tag factory with a `belongs_to` association for the book
            factory :tag do
            name { 'test_tag' }
            book

            trait :science do
            name { 'science' }
            end
            end

            # book factory with a `belongs_to` association for the author
            factory :book do
            title { "Through the Looking Glass" }
            author

            factory :science_book do
            title { "Some science stuff" }

            after(:create) do |book, evaluator|
            create(:tag, :science, book: book)
            end
            end
            end

            # author factory without associated books
            factory :author do
            name { "John Doe" }

            # author_with_science_books will create book data after the author has
            # been created
            factory :author_with_science_books do
            # books_count is declared as an ignored attribute and available in
            # attributes on the factory, as well as the callback via the evaluator
            transient do
            books_count { 5 }
            end

            # the after(:create) yields two values; the author instance itself and
            # the evaluator, which stores all values from the factory, including
            # ignored attributes; `create_list`'s second argument is the number of
            # records to create and we make sure the author is associated properly
            # to the book
            after(:create) do |author, evaluator|
            create_list(:science_book, evaluator.books_count, authors: [author])
            end
            end
            end
            end


            This allows you to do:



            create(:author).books.count # 0
            create(:author_with_science_books).books.count # 5
            create(:author_with_science_books, books_count: 15).books.count # 15


            So your test becomes:



            RSpec.describe Author, type: :model do
            describe '#has_science_tag?' do
            let(:author_with_science_books) { create(:author_with_science_books, books_count: 1) }

            context 'one science book' do
            it 'returns true' do
            expect(author_with_science_books.has_science_tag?).to eq true
            end
            end
            end
            end




            And you could also refactor Author#has_science_tag?:



            class Author < ApplicationRecord
            has_many :books

            def has_science_tag?
            books.joins(:tags).where("tags.name ILIKE '%science%'").exists?
            end
            end





            share|improve this answer






























              2














              Why dont you make use of FactoryBot associations?



              FactoryBot.define do
              # tag factory with a `belongs_to` association for the book
              factory :tag do
              name { 'test_tag' }
              book

              trait :science do
              name { 'science' }
              end
              end

              # book factory with a `belongs_to` association for the author
              factory :book do
              title { "Through the Looking Glass" }
              author

              factory :science_book do
              title { "Some science stuff" }

              after(:create) do |book, evaluator|
              create(:tag, :science, book: book)
              end
              end
              end

              # author factory without associated books
              factory :author do
              name { "John Doe" }

              # author_with_science_books will create book data after the author has
              # been created
              factory :author_with_science_books do
              # books_count is declared as an ignored attribute and available in
              # attributes on the factory, as well as the callback via the evaluator
              transient do
              books_count { 5 }
              end

              # the after(:create) yields two values; the author instance itself and
              # the evaluator, which stores all values from the factory, including
              # ignored attributes; `create_list`'s second argument is the number of
              # records to create and we make sure the author is associated properly
              # to the book
              after(:create) do |author, evaluator|
              create_list(:science_book, evaluator.books_count, authors: [author])
              end
              end
              end
              end


              This allows you to do:



              create(:author).books.count # 0
              create(:author_with_science_books).books.count # 5
              create(:author_with_science_books, books_count: 15).books.count # 15


              So your test becomes:



              RSpec.describe Author, type: :model do
              describe '#has_science_tag?' do
              let(:author_with_science_books) { create(:author_with_science_books, books_count: 1) }

              context 'one science book' do
              it 'returns true' do
              expect(author_with_science_books.has_science_tag?).to eq true
              end
              end
              end
              end




              And you could also refactor Author#has_science_tag?:



              class Author < ApplicationRecord
              has_many :books

              def has_science_tag?
              books.joins(:tags).where("tags.name ILIKE '%science%'").exists?
              end
              end





              share|improve this answer




























                2












                2








                2







                Why dont you make use of FactoryBot associations?



                FactoryBot.define do
                # tag factory with a `belongs_to` association for the book
                factory :tag do
                name { 'test_tag' }
                book

                trait :science do
                name { 'science' }
                end
                end

                # book factory with a `belongs_to` association for the author
                factory :book do
                title { "Through the Looking Glass" }
                author

                factory :science_book do
                title { "Some science stuff" }

                after(:create) do |book, evaluator|
                create(:tag, :science, book: book)
                end
                end
                end

                # author factory without associated books
                factory :author do
                name { "John Doe" }

                # author_with_science_books will create book data after the author has
                # been created
                factory :author_with_science_books do
                # books_count is declared as an ignored attribute and available in
                # attributes on the factory, as well as the callback via the evaluator
                transient do
                books_count { 5 }
                end

                # the after(:create) yields two values; the author instance itself and
                # the evaluator, which stores all values from the factory, including
                # ignored attributes; `create_list`'s second argument is the number of
                # records to create and we make sure the author is associated properly
                # to the book
                after(:create) do |author, evaluator|
                create_list(:science_book, evaluator.books_count, authors: [author])
                end
                end
                end
                end


                This allows you to do:



                create(:author).books.count # 0
                create(:author_with_science_books).books.count # 5
                create(:author_with_science_books, books_count: 15).books.count # 15


                So your test becomes:



                RSpec.describe Author, type: :model do
                describe '#has_science_tag?' do
                let(:author_with_science_books) { create(:author_with_science_books, books_count: 1) }

                context 'one science book' do
                it 'returns true' do
                expect(author_with_science_books.has_science_tag?).to eq true
                end
                end
                end
                end




                And you could also refactor Author#has_science_tag?:



                class Author < ApplicationRecord
                has_many :books

                def has_science_tag?
                books.joins(:tags).where("tags.name ILIKE '%science%'").exists?
                end
                end





                share|improve this answer















                Why dont you make use of FactoryBot associations?



                FactoryBot.define do
                # tag factory with a `belongs_to` association for the book
                factory :tag do
                name { 'test_tag' }
                book

                trait :science do
                name { 'science' }
                end
                end

                # book factory with a `belongs_to` association for the author
                factory :book do
                title { "Through the Looking Glass" }
                author

                factory :science_book do
                title { "Some science stuff" }

                after(:create) do |book, evaluator|
                create(:tag, :science, book: book)
                end
                end
                end

                # author factory without associated books
                factory :author do
                name { "John Doe" }

                # author_with_science_books will create book data after the author has
                # been created
                factory :author_with_science_books do
                # books_count is declared as an ignored attribute and available in
                # attributes on the factory, as well as the callback via the evaluator
                transient do
                books_count { 5 }
                end

                # the after(:create) yields two values; the author instance itself and
                # the evaluator, which stores all values from the factory, including
                # ignored attributes; `create_list`'s second argument is the number of
                # records to create and we make sure the author is associated properly
                # to the book
                after(:create) do |author, evaluator|
                create_list(:science_book, evaluator.books_count, authors: [author])
                end
                end
                end
                end


                This allows you to do:



                create(:author).books.count # 0
                create(:author_with_science_books).books.count # 5
                create(:author_with_science_books, books_count: 15).books.count # 15


                So your test becomes:



                RSpec.describe Author, type: :model do
                describe '#has_science_tag?' do
                let(:author_with_science_books) { create(:author_with_science_books, books_count: 1) }

                context 'one science book' do
                it 'returns true' do
                expect(author_with_science_books.has_science_tag?).to eq true
                end
                end
                end
                end




                And you could also refactor Author#has_science_tag?:



                class Author < ApplicationRecord
                has_many :books

                def has_science_tag?
                books.joins(:tags).where("tags.name ILIKE '%science%'").exists?
                end
                end






                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited Nov 21 '18 at 12:53

























                answered Nov 21 '18 at 12:31









                Martin ZinovskyMartin Zinovsky

                1,5241816




                1,5241816
































                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Stack Overflow!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53411752%2fhow-to-stub-has-many-association-in-rspec%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    MongoDB - Not Authorized To Execute Command

                    in spring boot 2.1 many test slices are not allowed anymore due to multiple @BootstrapWith

                    Npm cannot find a required file even through it is in the searched directory