Fixrb

Fix guide, version 1

Coming from RSpec

The same ideas under other names, and a few left out on purpose.

RSpec and Fix both describe behaviour with nested contexts and expectations. Two things differ at the root. A specification of Fix names no class: the subject comes from the test, on the last line, so that the same specification can test another one. And a specification is a document, read once when the file is loaded: it holds no code that runs before or after an expectation.

Structure

RSpecFix
RSpec.describe Counter do … endFix :Counter do … end
subject { Counter.new(start) }the block of the test: Fix[:Counter].test { Counter.new(start) }
describe "#count", then subject.count in each exampleon :count do … end
let(:start) { 0 }let(:start) { 0 }, visible from the subject block and the other lets only
context with let(:start) { 5 }with start: 5 do … end
before { subject.increment }on :increment do … end, around the expectations it prepares
it { is_expected.to eq(0) }it MUST eq 0
is_expected.not_toit MUST_NOT
shared_examples, it_behaves_likea specification of its own, required with fix(:Name), or tested against several subjects
rspec, rspec spec/modelsfix, fix fix/models
rspec --order randomfix --random, for the order of files
rspec --seed 1234fix --random=1234

An RSpec spec of a counter, with a subject, a before that increments it in one context and a let overridden in another, becomes:

require "fix"

class Counter
  attr_reader :count

  def initialize(start) = @count = start
  def increment = tap { @count += 1 }
end

Fix :Counter do
  let(:start) { 0 }

  on :count do
    it MUST eq 0
  end

  on :increment do
    on :count do
      it MUST eq 1
    end
  end

  with start: 5 do
    on :count do
      it MUST eq 5
    end
  end
end

exit(Fix[:Counter].test { Counter.new(start) })
ruby counter_fix.rb
counter_fix.rb:14: Success: expected 0 to eq 0
counter_fix.rb:19: Success: expected 1 to eq 1
counter_fix.rb:25: Success: expected 5 to eq 5

The before became a method of the chain: increment returns the counter, and count is sent to it. A method that needs some preparation beyond a chain gets it in the subject block, from the lets.

Matchers

RSpecFix
eq(x), eql(x)eq(x), which wants the class of x and uses eql?, where the eq of RSpec uses ==: 1.0 is not eq 1
equal(x), be(x)be(x)
be(true), be(false), be_nilbe_true, be_false, be_nil
be_truthy, be_falseysatisfy { |value| !!value }, satisfy { |value| !value }
be_a, be_kind_ofbe_a_kind_of
be_an_instance_of, be_instance_ofbe_an_instance_of
be_within(d).of(x)be_within(d).of(x)
match(regexp)match(regexp), for a String or a Symbol
satisfy { |v| … }, be >= 3satisfy { |v| … }, answering true or false
expect { … }.to raise_error(K)raise_exception(K), the method of the context being what raises
expect { … }.to change { o.count }.by(1)change(o, :count).by(1)
be_empty, and other predicateson(:empty?) { it MUST be_true }
include(x), start_with(x)on(:include?, x) { it MUST be_true }, on(:start_with?, x) { … }
have_attributes(name: "Ada")on(:name) { it MUST eq "Ada" }, or its(:name) { MUST eq "Ada" }
match_array, contain_exactlyon(:sort) { it MUST eq [1, 2, 3] }

Three levels instead of pending

RSpec skips an example, or marks it pending so that its failure does not fail the run. Fix says it in the specification itself: SHOULD for what is recommended, a warning when it is not met; MAY for what is optional, an info when the method does not exist. Both pass, and both are written in the lines, so nothing is hidden. Writing specifications has the five outcomes.

Left out on purpose

Both can live in one project, each in its directory: In a project tells how.