The Quest for Simplicity

One of the most striking differences between Fix and traditional testing frameworks like RSpec lies in their complexity. Let’s look at some numbers that tell an interesting story:

RSpec’s Components (version 3.3)

Total: 17,129 lines of code

Fix’s Components (version 0.7)

Total: 510 lines of code

The core philosophy is simple: a testing framework shouldn’t be more complex than the code it tests. This dramatic difference in code size (16,619 lines of code) reflects Fix’s commitment to minimalism and clarity.

A Real-World Comparison

Let’s look at a real-world example that demonstrates the key differences in approach. Consider this Monster class:

class Monster
  def self.get
    {
      boo: {
        name: "Boo",
        life: 123,
        mana: 42
      },
      hasu: {
        name: "Hasu",
        life: 88,
        mana: 40
      }
    }
  end

  def get(id)
    self.class.get.fetch(id)
  end
end

RSpec’s Layered Approach

require_relative "monster"
require "rspec/autorun"

RSpec.describe Monster do
  describe ".get" do
    subject(:monsters) { described_class.get }

    describe "#keys" do
      it { expect(monsters.keys).to eql %i(boo hasu) }
    end
  end

  describe ".new" do
    subject(:described_instance) { described_class.new }

    describe "#get" do
      subject(:monster) { described_instance.get(name) }

      context "with Boo monster" do
        let(:name) { :boo }
        it { expect(monster).to eql({ name: "Boo", life: 123, mana: 42 }) }
      end

      context "with Boom monster" do
        let(:name) { :boom }
        it { expect { monster }.to raise_exception KeyError }
      end
    end
  end
end

Fix’s Direct Style

require_relative "monster"
require "fix"

Fix.describe Monster do
  on :get do
    on :keys do
      it { MUST eql %i(boo hasu) }
    end
  end

  on :new do
    on :get, :boo do
      it { MUST eql({ name: "Boo", life: 123, mana: 42 }) }
    end

    on :get, :boom do
      it { MUST raise_exception KeyError }
    end
  end
end

Key Differentiators

  1. Method Chaining: Fix allows describing methods with one expression, whether for class or instance methods. This leads to more concise and readable code.

  2. Single Source of Truth: All specifications are derived from the described front object populated at the root. There’s no need for explicit or implicit subjects - there’s just one read-only dynamic subject deduced from the front object and described methods.

  3. Consistent Syntax: Fix maintains the same syntax regardless of what’s being tested. Whether you’re checking a value, expecting an error, or verifying a state change, the syntax remains uniform and predictable.

Clarity in Practice

Fix encourages a more direct and less ceremonial approach to testing. Compare how both frameworks handle error checking:

RSpec:

expect { problematic_call }.to raise_exception(ErrorType)

Fix:

it { MUST raise_exception ErrorType }

Or value comparison:

RSpec:

expect(value).to eq(expected)

Fix:

it { MUST eql expected }

This consistency helps reduce cognitive load and makes tests easier to write and understand.

Conclusion

Fix represents a fresh approach to Ruby testing that prioritizes simplicity and clarity. By reducing complexity and maintaining a consistent syntax, it helps developers focus on what matters: writing clear, maintainable tests that effectively verify their code’s behavior.

Want to try Fix for yourself? Get started with:

gem install fix

Visit our documentation to learn more about how Fix can improve your testing workflow.