How to Test Multiple WebSocket Sessions With Minitest and Rails 5 System Tests

Situation

You have a chat app that uses WebSockets for communication. There is a login system that dictates what socket you're subscribed to. With a normal system test, how would you ensure that messages are going back and forth in real time?

I'm assuming you're using Minitest and Rails 5 system tests with Capybara.

First Attempt

Use Capybara's open_new_window! This will create a new browser tab that I can sign in and interact with. I can also switch between tabs with within_window.

On the surface, this may work. However, depending on how you subscribe users to your sockets, the act of opening a tab, signing out, signing in as a new user, then sending a message may cause a lot of fuss within your app. Also, doesn't this just feel innately wrong?

My Solution

For those who don't know, you can create and switch between multiple Capybara sessions!

Creating a session will initialize a new window with completely separate cookies, localstorage, etc. This feels like a much cleaner and "more proper" solution. The default session name is, surprise, :default. You can get and set these via Capybara.session_name

Here's and example of how you'd achieve this by using a pseudo-test:

test 'Chatroom messages update in real time' do
  login_as @bob
  visit '/chatroom'

  Capybara.session_name = :new_window # This can be named whatever you'd like
  # We are in an entirely new browser session!
  login_as @sue
  visit '/chatroom'

  submit_message 'Hey, Bob!'

  Capybara.session_name = :default # We are now back in Bob's session as we left it

  assert page.has_content?('Hey, Bob!')

That feels a lot better to me! It also works much more reliably.

However, it's extremely ugly to use this method

If you want to save yourself some typing and improve your code readability, I like to put this simple method in my test_helper.rb or similar:

def within_session(name)
  old_session_name = Capybara.session_name
  Capybara.session_name = name.to_sym

  yield

  Capybara.session_name = old_session_name
end

And it can simplify the above test to something like this:

test 'Chatroom messages update in real time' do
  login_as @bob
  visit '/chatroom'

  within_session :new_window do
    login_as @sue
    visit '/chatroom'

    submit_message 'Hey, Bob!'
  end

  assert page.has_content?('Hey, Bob!')

Doesn't that look a lot better?

Cleanup

Finally, you may notice that the new browser windows remains open until your test suite finishes. This isn't really a problem, but I still don't like it. You can easily close windows with a method like this:

def close_session(name, original_session = :default)
  Capybara.session_name = name.to_sym

  page.driver.quit

  Capybara.session_name = original_session
end

Which can be used like so when you're done with a window:

close_session :new_window

Happy coding!