Search code examples
ruby-on-railsrspecrackrack-middleware

Testing Middleware with Rspec


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

Solution

  • 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