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

Problem with mock when using abstract class #42

Closed simaoneves closed 2 years ago

simaoneves commented 2 years ago

When using an abstract class in the following example, i get an error that the method register_hook is not being implemented.

require "./spec_helper"

abstract class SdkInterface
  abstract def register_hook(name, &block)
end

class Example
  def initialize(@sdk : Sdk)

  end

  def configure
    @sdk.register_hook("name") do
      nil
    end
  end
end

class Sdk < SdkInterface
  def initialize
  end

  def register_hook(name, &block)
    nil
  end
end

Spectator.describe Example do
  mock Sdk do
    stub register_hook(name, &block)
  end

  describe "#configure" do
    it "registers a block on configure" do
      sdk = Sdk.new
      example_class = Example.new(sdk)
      allow(sdk).to receive(register_hook())

      example_class.configure

      expect(sdk).to have_received(register_hook()).with("name")
    end
  end
end

If i change the Sdk class to not inherit from SdkInterface it works as expected.

icy-arctic-fox commented 2 years ago

Appears to be an issue with stubbing a method that takes a block. Removing all usages of &block and the block in Example#configure cause the spec to compile and pass. It looks like the injected stub code is omitting the block (probably never implemented). Since a method that takes a block is technically has a different signature, this explains the "not implemented" error.

I'm looking into the issue.

icy-arctic-fox commented 2 years ago

This should be addressed on master. Can you try that with your code to ensure it works as expected?

simaoneves commented 2 years ago

After updating to master, the example works great 😄 Many thanks!

I got into other problems though..

Screenshot 2022-02-22 at 22 57 35

with the following example:

require "./spec_helper"

abstract class SdkInterface
  abstract def register_hook(name, &block)
end

class Example
  def initialize(@sdk : Sdk)
  end

  def configure
    @sdk.register_hook("name") do

    end
  end
end

class Sdk < SdkInterface
  def initialize
  end

  def register_hook(name, &block)
    other_method(&block)
    nil
  end

  def other_method(block)
  end
end

Spectator.describe Example do
  mock Sdk do
    stub register_hook(name, &block)
  end

  describe "#configure" do
    it "registers a block on configure" do
      sdk = Sdk.new
      example_class = Example.new(sdk)
      allow(sdk).to receive(register_hook())

      example_class.configure

      expect(sdk).to have_received(register_hook()).with("name")
    end
  end
end

Should i open another issue?

icy-arctic-fox commented 2 years ago

No need for another issue. I'll try to get this fixed soon.

icy-arctic-fox commented 2 years ago

Providing an update here since it's been a while.

I have decided enough is enough and I've invested effort into redoing the entire mock and stub system. Instead of trying to fix this issue with the current system, it will be fixed in the new system. Progress on the new system is going well, but I don't have an estimate on when it will be ready.

icy-arctic-fox commented 2 years ago

The overhauled mock and double system is merged to master. There are some things to cleanup and document, then it will be ready for a release. Until the official docs are available, the DSL specs can be used as a reference.

icy-arctic-fox commented 2 years ago

This issue should be resolved in v0.11. There is a spec dedicated to it. If your issue isn't resolved, please reopen.