Testando o Método Create de um Controller com Nested Attributes

Como testar o método create de um controller que utiliza nested_attributes


Estava eu essa semana tentando terminar os scripts de teste de um projeto. Tudo estava dando certo, terminei de cobrir todos os testes dos models e comecei a trabalhar nos controllers, até que me deparei com o teste do método create de um controller que utiliza nested_attributes para cadastro de um model e suas associações. A estrutura dos models e controller é semelhante a essa:

# app/models/author.rb
class Author < ActiveRecord::Base
  has_many :posts, dependent: :destroy

  accepts_nested_attributes_for :posts, allow_destroy: true

  ...
end
# app/models/post.rb
class Post < ActiveRecord::Base
  belongs_to :author

  ...
end
# app/controllers/authors_controller.rb
class AuthorsController < ApplicationController
  ...

  def create
    @author = Author.new author_params

    if @author.save
      flash[:success] = 'Autor cadastrado com sucesso!'
      redirect_to @author
    else
      render 'new'
    end
  end

  ...

  private

  def author_params
    params.require(:author).permit(:name, posts_attributes: [
                                            :title, :content,
                                            :id, :_destroy
                                          ])
  end

  ...
end

Eu utilizo FactoryGirl para facilitar meus testes, a principio pesquisei por algum método mágico dessa gema que convertesse a Hash dos atributos exatamente para o aceito pelo parâmetro do controller (com nested_attributes). Passei longos 30 minutos e não achei nada, então resolvi fazer a seguinte gambiarra no script de teste:

require 'rails_helper'

RSpec.describe AuthorsController, type: :controller do
  ...

  describe 'POST #create' do
    context 'with valid attributes' do
      let!(author_params) do
        FactoryGirl.attributes_for(:author)
                   .merge(posts_attributes: [FactoryGirl.attributes_for(:post)])
      end

      it 'creates a new author' do
        expect {
          post :create, author: author_params
        }.to change { Author.count }.by(1)
      end

      it 'redirects to :show view' do
        post :create, author: author_params
        expect(response).to redirect_to(Author.last)
      end
    end

    ...
  end

  ...
end

Acredito que este não seja a forma mais correta de testar esse tipo de situação, mas pra mim deu certo.