Frage

I've written some Rack-Middleware and now I'm trying to test it with Rspec. But all Rack-Middleware is instantiated with an 'app' argument, that represents the Rails app itself. How do you guys mock this up in Rspec?

For example,

 describe MyMiddleWare do
    let(:app) { # How do I mock a Rails app object here? }
    subject { MyMiddleWare.new(app: app) }

    it 'should blah blah blah' do
       # a bunch of tests go here
    end
 end
War es hilfreich?

Lösung

You just need the world's simplest Rack app:

let(:app) { lambda {|env| [200, {'Content-Type' => 'text/plain'}, ['OK']]} }

Also, your middleware's constructor should receive an app as its first parameter not a hash so it should read:

subject { MyMiddleWare.new(app) }

In all likelihood, though, your test is going to need to determine what effect the middleware has had on the request. So you might write a slightly more sophisticated rack app to spy on your middleware.

class MockRackApp

  attr_reader :request_body

  def initialize
    @request_headers = {}
  end

  def call(env)
    @env = env
    @request_body = env['rack.input'].read
    [200, {'Content-Type' => 'text/plain'}, ['OK']]
  end

  def [](key)
    @env[key]
  end

end

and then you'll probably want to use Rack::MockRequest to actually send the request. Something like:

describe MyMiddleWare do

  let(:app) { MockRackApp.new }
  subject { described_class.new(app) }

  context "when called with a POST request" do
    let(:request) { Rack::MockRequest.new(subject) }
    before(:each) do
      request.post("/some/path", input: post_data, 'CONTENT_TYPE' => 'text/plain')
    end

    context "with some particular data" do
      let(:post_data) { "String or IO post data" }

      it "passes the request through unchanged" do
        expect(app['CONTENT_TYPE']).to eq('text/plain')
        expect(app['CONTENT_LENGTH'].to_i).to eq(post_data.length)
        expect(app.request_body).to eq(post_data)
      end
    end
  end
end

Andere Tipps

I believe you should use request specs simulating an http request (your middleware should be included in rails middleware stack). See more details on rspec request specs here.

UPD I think I've found exactly what you need, with Test::Unit, but it's easy to rewrite for RSpec: rack-ssl-enforcer

I tested mine like so

describe Support::CharConverter do

  let(:env_hash) do
    {
      "HTTP_REFERER" => "", 
      "PATH_INFO" => "foo",
      "QUERY_STRING" => "bar",
      "REQUEST_PATH" => "is",
      "REQUEST_URI" => "here",
    }
  end

  subject do 
    Support::CharConverter.new(env_hash)
  end

  context 'sanitize_env' do

    it 'should keep key values the same if nothing to sanitize' do
      sanitized_hash = subject.sanitize_env(env_hash)
      # k = env_hash.keys[5]
      # v = env_hash.values[5]
      env_hash.each do |k, v|
        sanitized_hash[k].encoding.name.should eq("US-ASCII")
        sanitized_hash[k].should eq(v)
        sanitized_hash[k].valid_encoding?.should eq(true)
      end
    end
  end
end

I'm writing a Sinatra app with a middleware and I followed @ritchie's path, and ended up creating an app that looks like this:

# Return the params in the response body as JSON
class ParamsInResponseApp < Sinatra::Base
  def call(env)
    super

    rack_params = env["rack.request.query_hash"]

    [200, {"Content-Type" => "application/json"}, [{"app" => rack_params}.to_json]]
  end
end

Inheriting from Sinatra::Base and calling super allowed me to get the params from env["rack.request.query_hash"], which was otherwise nil.

Note that this app puts the params in the response, so I can check them without having to stub anything.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top