Hacky test workaround for a Rails 8.1 change
- Date
- 23 March, 2026
- Category
- code
- Tags
- Ruby on Rails testing
I upgraded an app from Rails 8.0 to 8.1 today and things seemed to be going smashingly. The app was working like normal and my tests were passing -- wait, no, ONE test failed. A session controller test? Weird. Let's take a look.
When a user logs out of the app, we remove the session data and unset the Current.user.
def destroy
...
session.delete(:user_id)
session.delete(:user_token)
Current.user = nil
...
end
We had a couple tests making sure that this was happening. Simplified, here's what one of them looked like:
it "removes the current user" do
user = Fabricate(:user)
Current.user = user
session[:user_id] = user.id
get :destroy
assert_nil Current.user
assert_nil session[:user_id]
end
However, after the update to Rails 8.1, the first assertion was failing because Current.user was still set to the test user. I did a little debugging and I confirmed that the current user was being unset correctly in the controller, so why did the test still have the previously assigned value?
I went digging in the 8.1.x release notes and found what I was looking for in the 8.1.0 changelog:
Improve CurrentAttributes and ExecutionContext state managment in test cases.
Previously these two global state would be entirely cleared out whenever calling into code that is wrapped by the Rails executor, typically Action Controller or Active Job helpers...
Now re-entering the executor properly save and restore that state.
This tells me two things:
First, our previous test wasn't actually testing if the controller set Current.user to nil, it only appeared to be. So that's lovely.
Second, I was going to have to get more creative to make sure it was being set to nil, since no matter what happened in the controller, on the test side the value before a request was going to be restored.
The solution, such as it is
Thanks to searching the internet for one of my many, many previously clicked links about minitest, I came up with this tricky little fella.
it "checks that Current.user assignment set to nil" do
authenticate_as(users(:test_user))
verified = false
args_checker = lambda do |args|
assert_nil args
verified = true
end
Current.stub(:user=, args_checker) do
get :destroy
end
assert verified
end
Basically, Current.stub(:user=, args_checker) says hey, if anything tries to assign a value to Current.user, go ahead and use this stubbed in behavior instead. So when the controller comes across Current.user = nil, it runs args_checker instead. I put an assertion in there to make sure that the args it receives are nil.
One of my coworkers suggested, rightfully, that although I would get a warning of no assertions run if for some reason the stub wasn't working, the tests wouldn't actually fail. Therefore, we ended up with the verified variable.
If the lambda is never run, the value of verified remains false, and therefore the assert at the end will fail.
I think this is very goofy code. Does it work, though? Yes. And, even better, it replaces a test that was apparently silently passing. I can live with that.