Search code examples
ruby-on-railspostgresqltddassociationsminitest

How can I create new book and assign two authors with it using Rails miniTest?


Doing TDD

I facing a problem creating a book and assigning two authors with it. There is a failure in the creation. It says that there is an AssociationTypeMismatch: Author(#15100) expected, got nil which is an instance of NilClass(#40. I suppose that the format "authors: []" is not have been read well by something.

This is the test:

def test_has_many_and_belongs_to_mapping
 apress = Publisher.find_by(name: 'Apress')
 assert_equal 2, apress.books.size

 book = Book.new(
  title: 'Rails E-Commerce 3nd Edition',
  authors: [
    Author.find_by(first_name: 'Christian', last_name: 'Hellsten'),
    Author.find_by(first_name: 'Jarkko', last_name: 'Laine')
  ],
  published_at: Time.now,
  isbn: '123-123-123-x',
  blurb: 'E-Commerce on Rails',
  page_count: 300,
  price: 30.5
 )

 # apress.books << book

 # apress.reload
 # book.reload

 # assert_equal 3, apress.books.size
 # assert_equal 'Apress', book.publisher.name
end

This is the error:

enter image description here

MY ERD:

enter image description here

My schema:

enter image description here


Solution

  • I think what you are missing is a meaningful setup of data needed before this test runs. Even though those publishers and books may exist in your development environment/database, the test suite usually is configured to wipe the database clean between runs.

    If you have fixtures defined, then you can reference them like this:

    test/fixtures/publishers.yml

    apress:
      id: 1
      name: Apress
    

    test/fixtures/authors.yml

    hellsten:
      id: 1
      first_name: Christian
      last_name: Hellsten
    
    laine:
      id: 2
      first_name: Jarkko
      last_name: Laine
    

    test/fixtures/books.yml

    beginning_ruby:
      title: Beginning Ruby
      publisher_id: 1
    
    ruby_recipes:
      title: Ruby Recipes
      publisher_id: 1
    

    So that the test would look more like this:

    def test_has_many_and_belongs_to_mapping
     apress = publishers(:apress)
     assert_equal 2, apress.books.size
    
     book = Book.new(
      title: 'Rails E-Commerce 3nd Edition',
      authors: [
        Author.find_by(first_name: 'Christian', last_name: 'Hellsten'),
        Author.find_by(first_name: 'Jarkko', last_name: 'Laine')
      ],
      published_at: Time.now,
      isbn: '123-123-123-x',
      blurb: 'E-Commerce on Rails',
      page_count: 300,
      price: 30.5
     )
    
     apress.books << book
    
     apress.reload
     book.reload
    
     assert_equal 3, apress.books.size
     assert_equal 'Apress', book.publisher.name
    end
    

    Or instead of using fixtures, you could create the content before the test:

    So that the test would look more like this:

    setup do
      publisher = Publisher.create!(name: 'Apress')
      publisher.books.create!(title: 'Beginning Ruby')
      publisher.books.create!(title: 'Ruby Recipes')
      Author.create!(first_name: 'Christian', last_name: 'Hellsten')
      Author.create!(first_name: 'Jarkko', last_name: 'Laine')
    end
    
    def test_has_many_and_belongs_to_mapping
     apress = Publisher.find_by(name: 'Apress')
     assert_equal 2, apress.books.size
    
     book = Book.new(
      title: 'Rails E-Commerce 3nd Edition',
      authors: [
        Author.find_by(first_name: 'Christian', last_name: 'Hellsten'),
        Author.find_by(first_name: 'Jarkko', last_name: 'Laine')
      ],
      published_at: Time.now,
      isbn: '123-123-123-x',
      blurb: 'E-Commerce on Rails',
      page_count: 300,
      price: 30.5
     )
    
     apress.books << book
    
     apress.reload
     book.reload
    
     assert_equal 3, apress.books.size
     assert_equal 'Apress', book.publisher.name
    end
    

    That way your database has those values, and your find_by() calls should return data, and not nil.