icy-arctic-fox / spectator

Feature-rich testing framework for Crystal inspired by RSpec.
https://gitlab.com/arctic-fox/spectator
MIT License
103 stars 5 forks source link

How to test when exit() is called? #29

Closed postmodern closed 3 years ago

postmodern commented 3 years ago

Attempting to port some RSpec specs to Crystal which tests when the top-level exit method is called.

expect(subject).to receive(:exit).with(0)

Unfortunately, the Spectator method hook doesn't intercept the exit method call, and the process exits prematurely.

icy-arctic-fox commented 3 years ago

It seems that Spectator isn't stubbing the exit method. There needs to be a stub defined prior to calling receive(:exit). For instance:

class SomeClass
  def goodbye
    exit
  end
end

mock SomeClass do
  stub exit
end

it "captures exit" do
  expect(subject).to receive(:exit).with(0)
  subject.goodbye
end

This code doesn't work though, since exit isn't defined in SomeClass - it's a top-level method. Another issue is that a type is required for the mock block, but there isn't one for a top-level method. (maybe Object?)

Do you have any code that shows this in action in RSpec?

postmodern commented 3 years ago

Code: https://github.com/postmodern/command_kit/blob/a06a131ddde42fa7f7730debbdc66136282bae1f/lib/command_kit/main.rb#L46-L56 RSpec specs: https://github.com/postmodern/command_kit/blob/a06a131ddde42fa7f7730debbdc66136282bae1f/spec/main_spec.rb#L28-L100

icy-arctic-fox commented 3 years ago

This should be possible in v0.9.39 - Can you try it out and confirm?

The following spec works:

require "../spec_helper"

Spectator.describe "GitHub Issue #29" do
  class SomeClass
    def goodbye
      exit 0
    end
  end

  mock SomeClass do
    stub exit(code)
  end

  describe SomeClass do
    it "captures exit" do
      expect(subject).to receive(:exit).with(0)
      subject.goodbye
    end
  end
end
postmodern commented 3 years ago

Ah, stubbing/capturing exit now works on instances, but not at the class-level. I'm guessing mock/stub is assuming exit is an instance method?

require "./spec_helper"

Spectator.describe "test exit" do
  class Foo
    def self.test
      exit 0
    end
  end

  mock Foo do
    stub exit(code)
  end

  subject { Foo }

  it "must capture exit" do
    expect(subject).to receive(:exit).with(0)

    subject.test
  end
end
$ crystal spec spec/test_spec.cr 
test exit
icy-arctic-fox commented 3 years ago

I think I see why this is happening. I'll try to get a fix in.

icy-arctic-fox commented 3 years ago

Should be resolved in v0.9.40, can you verify?

For class methods that call exit, it will need to stub self.exit(code).

postmodern commented 3 years ago

Confirmed self.exit(code) works.

icy-arctic-fox commented 2 years ago

Heads up, this is changing in v0.11. Instead of relying on stubs, exit will raise a Spectator::SystemExit error. This mimics what RSpec does. See the related spec for this issue for an example.