Skip to content

Instantly share code, notes, and snippets.

@themoxman
Last active August 29, 2015 13:55
Show Gist options
  • Save themoxman/8760301 to your computer and use it in GitHub Desktop.
Save themoxman/8760301 to your computer and use it in GitHub Desktop.

Testing controller 'helper' type methods

I have a couple methods defined in controllers that I call in one of the controller actions.

Example from SearchController:

I've been testing these methods by actually making a request. That can't be the most efficient way to test them. I can't exactly call ApplicationController.assigns_session_tags and then test the result...

Testing AR collections

controller:

  def index
    @tag_list = @collection.tags
    @tag = @collection.tags.new
  end

spec:

  describe "GET #index", unit: true do
    it "assigns all Tags to @tags" do
      tags = %w(Rails Ruby ActiveRecord Haml)
      tags.map! { |tag| @collection.tags.create(name: tag) }
      mo = Tag.create(name: "Model")
      get :index, collection_id: @collection
      expect(assigns(:tag_list)).to match_array(tags)
    end

error:

  1) TagsController GET #index assigns all Tags to @tags
     Failure/Error: expect(assigns(:tag_list)).to match_array(tags)
       expected collection contained:  [#<Tag id: 1, name: "Rails", created_at: "2014-02-05 16:53:30", updated_at: "2014-02-05 16:53:30", collection_id: 1>, #<Tag id: 2, name: "Ruby", created_at: "2014-02-05 16:53:30", updated_at: "2014-02-05 16:53:30", collection_id: 1>, #<Tag id: 3, name: "ActiveRecord", created_at: "2014-02-05 16:53:30", updated_at: "2014-02-05 16:53:30", collection_id: 1>, #<Tag id: 4, name: "Haml", created_at: "2014-02-05 16:53:30", updated_at: "2014-02-05 16:53:30", collection_id: 1>]
       actual collection contained:    #<ActiveRecord::Associations::CollectionProxy [#<Tag id: 4, name: "Haml", created_at: "2014-02-05 16:53:30", updated_at: "2014-02-05 16:53:30", collection_id: 1>, #<Tag id: 3, name: "ActiveRecord", created_at: "2014-02-05 16:53:30", updated_at: "2014-02-05 16:53:30", collection_id: 1>, #<Tag id: 2, name: "Ruby", created_at: "2014-02-05 16:53:30", updated_at: "2014-02-05 16:53:30", collection_id: 1>, #<Tag id: 1, name: "Rails", created_at: "2014-02-05 16:53:30", updated_at: "2014-02-05 16:53:30", collection_id: 1>, #<Tag id: nil, name: nil, created_at: nil, updated_at: nil, collection_id: 1>]>
       the extra elements were:        [#<Tag id: nil, name: nil, created_at: nil, updated_at: nil, collection_id: 1>]

my solution:

    it "assigns all Tags to @tags" do
      ra = @collection.tags.create(name: "Rails")
      ru = @collection.tags.create(name: "Ruby")
      ac = @collection.tags.create(name: "ActiveRecord")
      mo = Tag.create(name: "Model")

      get :index, collection_id: @collection
      expect(assigns(:tag_list)).to include(ra, ru, ac)
      expect(assigns(:tag_list)).not_to include(mo)
    end

HTTP responses

I could not get around this response in an RSpec controller test.

  1) NotesController DELETE #destroy redirects to SearchController#results
     Failure/Error: expect(response).to redirect_to results_search_path
       Expected response to be a <redirect>, but was <200>

spec:

  describe "DELETE #destroy" do
    it "removes the tag from the database" do
      expect{
        delete :destroy, { collection_id: @collection, id: @note }
      }.to change(@collection.notes, :count).by(-1)
    end

    it "redirects to SearchController#results" do
      expect(response).to redirect_to results_search_path
    end
  end

NotesController#destroy

  def destroy
    @note.destroy
    render results_search_path, notice: "Note deleted"
  end

Testing Nested Resource Associations

See app/controllers/notes_controller.rb and spec/controllers/notes_controller_spec.rb.

How would you test that @collection.tags and @collection.notes are functioning properly?

  def new
    @note = @collection.notes.new
    @tag_list = @collection.tags
  end

instead of this:

  def new
    @note = Note.new
    @tag_list = Tag.all
  end
  describe "GET #new", unit: true do
    it "assigns a new Note to @note" do
      get :new, { collection_id: @collection.id }
      expect(assigns(:note)).to be_a_new(Note)
    end

    it "assigns @note to the current Collection" do
      get :new, { collection_id: @collection.id }
      expect(assigns(:note).collection).to eq(@collection)      
    end

    it "assigns all tags to @tag_list" do
      ra = @collection.tags.create( name: "Rails")
      ru = @collection.tags.create( name: "Ruby")
      po = @collection.tags.create( name: "Postgres")

      get :new, { collection_id: @collection.id }
      expect(assigns(:tag_list)).to match_array(@collection.tags)
      expect(assigns(:tag_list)).to have(3).items
    end
  end

Helper Modules

I'm not sure if all the files I have in app/models/ should be there. search_handler.rb, tag_handler.rb and tag_util.rb aren't even ActiveRecord models. Those files provide functionality that I didn't want in the AR models. Where should those 3 files live?

Also, I'm not sure about the way in which I built them. I created them to pull query related processes out of my controllers. Originally there was only, tag_handler.rb. Then, as I was writing search_handler.rb I realized I was basically going to have to copy some functionality from tag_handler.rb. Although, looking at things now, the only functionality truly shared by both TagHandler and SearchHandler is the parse_tags method. Anyway, at the time, it seemed that I could pull out some of this Tag related stuff into a TagUtil module. It almost seems like I separated things too much. You can see how I use these two classes and module in my NotesController#create and SearchController#results actions.

And, how do the app/helpers/ modules play into all this?

DB Structure

I'm having to re-think the structure of the app. Originally I just had Notes, Tags and Taggings models. Taggings, as I'm sure you guess provides me with 'has_many through' associations for both Notes and Tags. Notes have many Tags and Tags have many Notes. Then, I added in User auth with Devise. I could give a User many Notes and many Tags, but I decided I wanted to add in the idea of Collections, groups of related notes. For instance, I don't want notes/tags for programming and notes/tags for leadership hanging out together. That gets messy, especially the tag cloud aspect.

So, I'm currently building the app out so that Users have Collections, and Collections have Notes and Tags. Notes and Tags will be nested resources of Collections. Which brings me to the next question, authorization.

Authorization

Routes for any user are going to look something like this:

        collection_notes GET      /collections/:collection_id/notes(.:format)          notes#index
                         POST     /collections/:collection_id/notes(.:format)          notes#create
     new_collection_note GET      /collections/:collection_id/notes/new(.:format)      notes#new
    edit_collection_note GET      /collections/:collection_id/notes/:id/edit(.:format) notes#edit
         collection_note GET      /collections/:collection_id/notes/:id(.:format)      notes#show
                         PATCH    /collections/:collection_id/notes/:id(.:format)      notes#update
                         PUT      /collections/:collection_id/notes/:id(.:format)      notes#update
                         DELETE   /collections/:collection_id/notes/:id(.:format)      notes#destroy
         collect
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment