ruby-on-railsrspecrspec-railswebmock

Is there a way to test whether a template has properties in an RSpec controller test?


So I'm working on my first Rails project and am working on getting some automated testing set up. Right now, I have a template for a form which has a select tag, which is getting its information dynamically based on a mocked API response. As of now, my template successfully renders, but I would like to be able to access that select tag and see if its child options match with the my mock API response. Haven't been able to find a whole lot in the docs about this particular use case, hoping someone here may be able to point me in the right direction. Here's essentially what I have so far:

RSpec.describe MyController, type: :controller, do
  describe "GET my_thing/index" do
    it "renders the index template" do
      stub_request(:get, "https://outsideapi.net/endpoint").
        to_return(status: 200,
                  body: {
                    "values" => [
                      { "name": "foo", "id": "10000" },
                      { "name": "bar", "id": "10001" },
                      { "name": "baz", "id": "10002" },
                     ],
                  }.to_json,
                  headers: {})

      get :index, params: { request_identifier: generate_request_identifier() }
      expect(response).to have_http_status(:success)
      expect(response).to render_template(:index)
      # Some code that will hopefully get elements off the template
    end
  end

Solution

  • According to Rspec docs for controller tests:

    by default, views are not rendered. See views are stubbed by default and render_views for details.

    So first things first, you'll want to instruct Rspec to render your controller views by adding a render_views directive in your test suite as mentioned here:

    require "rails_helper"
    
    RSpec.describe WidgetsController, :type => :controller do
      render_views # <-- This
    
      describe "GET index" do
        it "has a widgets related heading" do
          get :index
          expect(response.body).to match /<h1>.*widgets/im
        end
      end
    end
    

    Finally, you'll be able to match the JSON array with the rendered HTML using somthing like this(code is untested, but it should hopefully drive the point):

    
    RSpec.describe MyController, type: :controller, do
      describe "GET my_thing/index" do
        it "renders the index template" do
          # Can also be added below the `describe` call if view rendering is needed elsewhere.
          render_views
          select_values = [
                          { "name": "foo", "id": "10000" },
                          { "name": "bar", "id": "10001" },
                          { "name": "baz", "id": "10002" },
                         ]
          stub_request(:get, "https://outsideapi.net/endpoint").
            to_return(status: 200,
                      body: {
                        "values" => select_values,
                      }.to_json,
                      headers: {})
    
          get :index, params: { request_identifier: generate_request_identifier() }
          expect(response).to have_http_status(:success)
          expect(response).to render_template(:index)
          # Extract the select value names as an array of strings.
          select_value_names = select_values.map {|v| v['name']}
          expect(response.body).to include(select_value_names)
        end
      end
    

    On a related note, if you're running tests against page contents, you might want to consider using acceptance testing frameworks such as Capybara, which integrates well with Rspec.