The RSpec you know and love with xUnit syntax.

BDD

require "rails_helper"

RSpec.describe Next::LoginForm do
  it "validates a user can be found by email" do
    login = Next::LoginForm.submit email: "[email protected]", password: ":(",

    expect(login).to be_invalid
    expect(login.errors.details[:base]).to eq([error: :not_found])
  end

  it "validates a user can be authenticated" do
    user = create :user

    expect {
      login = Next::LoginForm.submit email: user.email, password: user.password

      expect(login).to be_valid
    }.to change { Session.count }.by(1)
  end
end

🤢

xUnit

require "test_helper"

RSpec.case Next::LoginForm do
  test "user cannot be found" do
     = Next::LoginForm.submit email: "[email protected]", password: ":("

    assert_invalid? 
    assert_eq .errors.details[:base], [error: :not_found]
  end

  test "user authentication" do
    user = create :user

    assert_change Session, :count do
       = Next::LoginForm.submit email: user.email, password: user.password

      assert_valid? 
    end
  end
end

🥳

Installation

Add rspec-xunit to both the :development and :test groups of your Gemfile:

group :development, :test do
  gem 'rspec-xunit'
end

If you want your tests in the test directory (as you should, my friend) put this in your .rspec file:

-Itest
--pattern='test/**/*_test.rb'

Syntax

The syntax offered by rspec-xunit should be familiar to xUnit testing framework users like minitest.

You start a test case by RSpec.case. This is a simple alias of Rspec.describe. You define individual tests (what you call examples in your past life) with the test macro. It is an alias of it and it supports all it's goodies, like skipping a test with xtest.

This leaves us to at the most important change in rspec-xunit... You no longer write expectations but assertions!

Assertions

Let's take a look at single RSpec BDD example:

it "validates a user can be found by email" do
  login = Next::LoginForm.submit email: "[email protected]", password: ":(",

  expect(login).to be_invalid
  expect(login.errors.details[:base]).to eq([error: :not_found])
end

The line expect(login).to be_invalid as an expectation. The be_invalid part of it is a matcher. For every RSpec matcher, we have a corresponding assertion. This is how we can rewrite the example above as a test:

test "user cannot be found" do
   = Next::LoginForm.submit email: "[email protected]", password: ":("

  assert_invalid? 
  assert_eq .errors.details[:base], [error: :not_found]
end

The assertions provided by rspec-xunit follow the pattern assert_:matcher, where :matcher is a name of standard RSpec matcher. This way, every matcher you expect from RSpec is already available in rspec-xunit. 🎉

We even support block matchers like:

test "missing required fields" do
  assert_raise_error ActiveModel::ValidationError do
    Next::LoginForm.submit!
  end
end

The test above is equivalent to the example below:

it "requires email and password present" do
  expect {
    Next::LoginForm.submit!
  }.to raise_error(ActiveModel::ValidationError)
end

We have the aliases of assert_raise and assert_raises to assert_raise_error for that extra bittersweet xUnit feel. 🤤

Some block-level assertions, though are hard to convert. Take this example, for example 😉:

it "validates a user can be authenticated" do
  user = create :user

  expect {
     = Next::LoginForm.submit email: user.email, password: user.password

    expect().to be_valid
  }.to change { Session.count }.by(1)
end

We cannot translate change { Session.count }.by(1) to a nice assertion. This is where the assert fall-back comes in:

test "user authentication" do
  user = create :user

  assert {
    assert_valid? 
  }.to change { Session.count }.by(1)
end

This looks suspiciously like expect because it is its alias! 🙄 Sometimes you just gotta expect, I mean assert, you know!

Have you noticed the assert_valid? login line? We call it an assertion predicate! Every assertion ending in a question-mark invokes that predicate method on the asserted object.

assert_empty? object_responding_to_empty_question_mark