Skip to content
GitLab
    • GitLab: the DevOps platform
    • Explore GitLab
    • Install GitLab
    • How GitLab compares
    • Get started
    • GitLab docs
    • GitLab Learn
  • Pricing
  • Talk to an expert
  • /
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
    • Switch to GitLab Next
    Projects Groups Topics Snippets
  • Register
  • Sign in
  • Spectator Spectator
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributor statistics
    • Graph
    • Compare revisions
  • Issues 25
    • Issues 25
    • List
    • Boards
    • Service Desk
    • Milestones
  • Merge requests 0
    • Merge requests 0
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Artifacts
    • Schedules
  • Deployments
    • Deployments
    • Releases
  • Packages and registries
    • Packages and registries
    • Model experiments
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Repository
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • Mike Miller
  • SpectatorSpectator
  • Issues
  • #51
Closed
Open
Issue created May 09, 2020 by Stergio@stergiom

Inconsistencies with union types when stubbing.

A couple of inconsistencies;

  • When a method returns a specific type, that type is expected irrespective of whether said method has an explicit union type return definition. This breaks when mocking and allowing for a different return type (of the union type defined). This might be intentional, the case in which it's tripped up for me is where mock objects are given as dependencies which contain canned responses of a certain type. I'm getting around it in a similar way to how #alt1_call works where #call does not.

  • Having an allow which breaks, defined in before_each, seems to get in the way of other allows defined in this same before_each.

Bar
  #call
    ✅ invokes Foo#call
    ✅ invokes Foo#alt1_call
    ✅ invokes Foo#alt2_call
    with an explicit return of nil
      ❌ should invoke Foo#call?
      ✅ invokes Foo#alt1_call
      ✅ invokes Foo#alt2_call
    with returns set in before_each for all calls
      ❌ should invoke Foo#call?
      ❌ should invoke Foo#alt1_call?
      ❌ should invoke Foo#alt2_call?
    with returns set in before_each for alt calls only
      ✅ invokes Foo#alt1_call
      ✅ invokes Foo#alt2_call
class Foo
  def call(str : String) : String?
    ""
  end

  def alt1_call(str : String) : String?
    nil
  end

  def alt2_call(str : String) : String?
    [str, nil].sample
  end
end

class Bar
  def call(a_foo)
    a_foo.call("")
    a_foo.alt1_call("")
    a_foo.alt2_call("")
  end
end

Spectator.describe Bar do
  mock Foo do
    stub call(str : String)      { "" }
    stub alt1_call(str : String) { "" }
    stub alt2_call(str : String) { "" }
  end

  let(:foo) { Foo.new}
  subject(:call) { described_class.new.call(foo) }

  describe "#call" do
    it "invokes Foo#call" do
      call
      expect(foo).to have_received(:call)
    end

    it "invokes Foo#alt1_call" do
      call
      expect(foo).to have_received(:alt1_call)
    end

    it "invokes Foo#alt2_call" do
      call
      expect(foo).to have_received(:alt2_call)
    end

    describe "with an explicit return of nil" do
      it "should invoke Foo#call?" do
        allow(foo).to receive(:call).and_return(nil)
        call
        expect(foo).to have_received(:call)
      end

      it "invokes Foo#alt1_call" do
        allow(foo).to receive(:alt1_call).and_return(nil)
        call
        expect(foo).to have_received(:alt1_call)
      end

      it "invokes Foo#alt2_call" do
        allow(foo).to receive(:alt2_call).and_return(nil)
        call
        expect(foo).to have_received(:alt2_call)
      end
    end

    describe "with returns set in before_each for all calls" do

      before_each do
        allow(foo).to receive(:call).and_return(nil)
        allow(foo).to receive(:alt1_call).and_return(nil)
        allow(foo).to receive(:alt2_call).and_return(nil)
      end

      it "should invoke Foo#call?" do
        call
        expect(foo).to have_received(:call)
      end

      it "should invoke Foo#alt1_call?" do
        call
        expect(foo).to have_received(:alt1_call)
      end

      it "should invoke Foo#alt2_call?" do
        call
        expect(foo).to have_received(:alt2_call)
      end
    end

    describe "with returns set in before_each for alt calls only" do

      before_each do
        allow(foo).to receive(:alt1_call).and_return(nil)
        allow(foo).to receive(:alt2_call).and_return(nil)
      end

      it "invokes Foo#alt1_call" do
        call
        expect(foo).to have_received(:alt1_call)
      end

      it "invokes Foo#alt2_call" do
        call
        expect(foo).to have_received(:alt2_call)
      end
    end
  end
end
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information
Assignee
Assign to
Time tracking