Have you ever successfully used redirect_back_or_to in your application only to find out that testing was a challenge? Both methods rely on browser history which is generally not available within controller test and request specs. Recently I ran into this and after digging in came up with a simple solution.

First what is redirect_back_or_to

redirect_back_or_to and its deprecated alias redirect_back both have the goal of request flow control. Depending on what information is available in browser history they can either redirect to the previously visited page or to a specified fallback location. In both cases, the fallback location is required. This was the primary issue I ran into while testing.

Let’s take a look at an example. Note: We’ll be looking at Minitest as a testing framework but the concepts explored here should function for RSpec as well.

Quick example

Let’s say you have the following Minitest controller test. Our desired behavior is that when a user updates their password but does not include a number it should redirect them and display a error message.

We’re using redirect_back_or_to in our code with a fallback location of root_path

redirect_back_or_to root_path

Our controller test looks like the following:

  context "PATCH #update" do
    should "be invalid with password in wrong format" do
      sign_in @user

      patch user_passwords_path, params: { id: @user.id, user: { password: "invalid without numbers" } }

      assert_redirected_to edit_user_passwords_path
    end
  end

Now the above looks accurate but the problem with controller tests there is no context as the user’s last visited location. We’re essentially sending a request directly to an endpoint above without first visiting the edit_user_passwords_path which is typically how a user would interact. The result of the above is a failing test as instead of being redirected to the edit page we end up on the root_path. Not ideal.

We know the issue is a lack of browser history for redirect_back_or_to to utilize. This is the first nod to how the method works. Also, the documentation gives a great hint as well:

Redirects the browser to the page that issued the request (the referrer) if possible, otherwise redirects to the provided default fallback location. The referrer information is pulled from the HTTP Referer (sic) header on the request. This is an optional header and its presence on the request is subject to browser security settings and user preferences. If the request is missing this header, the fallback_location will be used.

api.rubyonrails.org

Basically the only way that redirect_back_or_to can reliably navigate the user back in history is to look at the Referer header. With that knowledge we’ve got a solution.

The solution

We only need to adjust the original test slightly to get the desired result. Focusing on including the referer header when making the endpoint request ensures that we redirect to the correct location.

patch user_passwords_path, params:
  {
    id: @user.id,
    user: {
      password: "invalid without numbers"
    }
  },
  headers: {
    referer: edit_user_passwords_url
  }

Note that instead of using edit_user_passwords_path we instead utilize the fully qualified url with the _url helper. This ensures that our test behaves the same way that the browser would.

With that everything is green and good to go.

Thanks for reading.

« Previous Post
Tips I learned working with ActiveStorage
Next Post »
Simulating a select dropdown change in Jest

Join the conversation

comments powered by Disqus