Code Monkey home page Code Monkey logo

Comments (8)

icy-arctic-fox avatar icy-arctic-fox commented on May 27, 2024 1

It looks like the old (v0.9) stub syntax is being used. A method definition (def) is required after the stub keyword. That should have raised a compiler error, so I'll look into that...

    mock Chars::CharSet do
      stub :|, other : Chars::CharSet
    end

# can be just:

mock Chars::CharSet

The v0.11 syntax doesn't require a stub entry for each stubbed method. The mocked type also has to be instantiated with mock(Type).

I think this code should work:

Spectator.describe Chars::CharSet do
  mock Chars::CharSet

  describe "#|" do
    let(other) { mock(Chars::CharSet) }
    let(return_value) { mock(Chars::CharSet) }

    it "must call #+" do
      expect(subject).to receive(:|).with(other).and_return(return_value)

      expect(subject | other).to be(return_value)
    end
  end
end

More info about the new syntax and mock system can be found on the wiki.

from spectator.

icy-arctic-fox avatar icy-arctic-fox commented on May 27, 2024 1

I was attempting to copy the code you had before. This bit from the original:

    let(other) { described_class.new }
    let(return_value) { described_class.new }

The mock outside of the it block is a macro that defines a sub-type which mocks the original. The mock inside of a test block (or any other function like let) instantiates the previously defined mock. These can be replaced with def_mock and new_mock respectively if it makes more sense.

Spectator.describe Chars::CharSet do
  def_mock Chars::CharSet

  let(other) { new_mock(Chars::CharSet) }
  let(return_value) { new_mock(Chars::CharSet) }
end

Hope that makes sense. 😕

I'm glad it works now! Real world usages of Spectator are invaluable for testing it.

from spectator.

icy-arctic-fox avatar icy-arctic-fox commented on May 27, 2024

It appears that the mock code doesn't account for free variables in return types.

from spectator.

icy-arctic-fox avatar icy-arctic-fox commented on May 27, 2024

A fix for this issue and various related ones has been pushed to master. Can you confirm this fixes the issue with free variables in type annotations? I suspect there may be other issues porting chars.cr to the latest Spectator mocks. Please open an issue if you encounter any.

from spectator.

postmodern avatar postmodern commented on May 27, 2024

@icy-arctic-fox awesome, that got me further. I now think I'm hitting another spectator bug.

Example code:

    #
    # Returns an Array of random, but unique, bytes from the `CharSet`.
    #
    def random_distinct_bytes(length : Int) : Array(Int32)
      @byte_set.to_a.shuffle[0,length]
    end

    #
    # Returns an Array of random, but unique, bytes from the `CharSet`.
    #
    def random_distinct_bytes(length : Array(Int),
                              random = Random::DEFAULT) : Array(Int32)
      bytes.shuffle[0,length.sample(random)]
    end

    #
    # Returns an Array of random, but unique, bytes from the `CharSet`.
    #
    def random_distinct_bytes(length : Range(Int, Int)) : Array(Int32)
      @byte_set.to_a.shuffle[0,rand(length)]
    end

Example spec code:

  describe "#random_distinct_bytes" do
    it "must return a random Array of unique bytes" do
      bytes = subject.random_distinct_bytes(10)

      expect(bytes.uniq).to be == bytes
      expect(bytes.all? { |b| subject.includes?(b) }).to be(true)
    end

    context "with a range of lengths" do
      it "must return a random Array of unique bytes with a varying length" do
        bytes = subject.random_distinct_bytes(5..10)

        expect(bytes.uniq).to be == bytes
        expect(bytes.size).to be_between(5, 10)
        expect(bytes.all? { |b| subject.includes?(b) }).to be(true)
      end
    end
  end

Error:

...

There was a problem expanding macro 'default_stub'

Called macro defined in lib/spectator/src/spectator/mocks/stubbable.cr:108:13

 108 | private macro default_stub(method)

Which expanded to:

    1 |       
    2 |       
    3 |       
    4 |       
    5 | 
    6 |        def random_distinct_bytes(
    7 |         length : Range(Int, Int), 
    8 |         
    9 |         
   10 |       ) : Array(Int32)
   11 |         super(length)
   12 |       end
   13 | 
   14 |       
   15 | 
   16 |       
   17 |        def random_distinct_bytes(
   18 |         length : Range(Int, Int), 
   19 |         
   20 |         
   21 |       ) : Array(Int32)
   22 | 
   23 |         # Capture information about the call.
 > 24 |         __temp_1575 = ::Spectator::MethodCall.build(
   25 |           :random_distinct_bytes,
   26 |           ::NamedTuple.new(
   27 |             "length": length, 
   28 |           ),
   29 |           
   30 |           ::NamedTuple.new(
   31 |             
   32 |           ).merge()
   33 |         )
   34 |         _spectator_record_call(__temp_1575)
   35 | 
   36 |         # Attempt to find a stub that satisfies the method call and arguments.
   37 |         # Finding a suitable stub is delegated to the type including the `Stubbable` module.
   38 |         if __temp_1576 = _spectator_find_stub(__temp_1575)
   39 |           # Cast the stub or return value to the expected type.
   40 |           # This is necessary to match the expected return type of the original method.
   41 |           _spectator_cast_stub_value(__temp_1576, __temp_1575, typeof(previous_def),
   42 |           :raise)
   43 |         else
   44 |           # Delegate missing stub behavior to concrete type.
   45 |           _spectator_stub_fallback(__temp_1575, typeof(previous_def)) do
   46 |             # Use the default response for the method.
   47 |             previous_def
   48 |           end
   49 |         end
   50 |       end
   51 |     
Error: instantiating 'Spectator::MethodCall.class#build(Symbol, NamedTuple(length: Range(Int32, Int32)), NamedTuple())'


In lib/spectator/src/spectator/mocks/method_call.cr:27:35

 27 | arguments = FormalArguments.build(*args, **kwargs).as(AbstractArguments)
                                  ^----
Error: instantiating 'Spectator::FormalArguments(Args, Splat, DoubleSplat).class#build(Tuple(NamedTuple(length: Range(Int32, Int32)), NamedTuple()), NamedTuple())'


In lib/spectator/src/spectator/mocks/formal_arguments.cr:36:7

 36 | new(args, nil, nil, kwargs)
      ^--
Error: instantiating 'new(NamedTuple(length: Range(Int32, Int32)), Nil, Nil, NamedTuple())'


Error: instantiating 'Spectator::AbstractArguments+#===(Spectator::AbstractArguments+)'


In lib/spectator/src/spectator/mocks/stub.cr:47:18

 47 | constraint === call.arguments
                 ^-
Error: instantiating 'Spectator::AbstractArguments+#===(Spectator::AbstractArguments+)'


In lib/spectator/src/spectator/mocks/formal_arguments.cr:130:7

 130 | compare_named_tuples(args, other.args) && compare_tuples(splat, other.splat) && compare_named_tuples(kwargs, other.kwargs)
       ^-------------------
Error: instantiating 'compare_named_tuples(NamedTuple(length: Range(Int32, Int32)), (NamedTuple() | NamedTuple(byte: Int32 | UInt8) | NamedTuple(char: Char) | NamedTuple(io: String::Builder) | NamedTuple(length: Int32 | Range(Int32, Int32)) | NamedTuple(length: Int32, random: Random::PCG32) | NamedTuple(method: Symbol) | NamedTuple(n: Int32) | NamedTuple(random: Random::PCG32) | NamedTuple(value: Char)))'


In lib/spectator/src/spectator/mocks/abstract_arguments.cr:35:9

 35 | a.each do |k, v1|
        ^---
Error: instantiating 'NamedTuple(length: Range(Int32, Int32))#each()'


In lib/spectator/src/spectator/mocks/abstract_arguments.cr:35:9

 35 | a.each do |k, v1|
        ^---
Error: instantiating 'NamedTuple(length: Range(Int32, Int32))#each()'


In lib/spectator/src/spectator/mocks/abstract_arguments.cr:42:34

 42 | return false unless v1 === v2
                             ^-
Error: instantiating 'Range(Int32, Int32)#===((Char | Int32 | Random::PCG32 | Range(Int32, Int32) | String::Builder | Symbol | UInt8))'


In /var/lib/snapd/snap/crystal/1384/share/crystal/src/range.cr:327:5

 327 | includes?(value)
       ^--------
Error: instantiating 'includes?((Char | Int32 | Random::PCG32 | Range(Int32, Int32) | String::Builder | Symbol | UInt8))'


In /var/lib/snapd/snap/crystal/1384/share/crystal/src/range.cr:298:35

 298 | (begin_value.nil? || value >= begin_value) &&
                                     ^----------
Error: expected argument #1 to 'Char#>=' to be Char, not Int32

Overloads are:
 - Char#>=(other : Char)
 - Comparable(T)#>=(other : T)

errors.log

from spectator.

icy-arctic-fox avatar icy-arctic-fox commented on May 27, 2024

Pushed another change to master that should fix range comparisons.

The underlying issue is that a union of argument types are being compared against each other. Case-equality matching (===) is used. This creates all sorts of combinations like (in your case):

(1..10) === 'a'

Which doesn't make sense, but could be a possible comparison with the union types. What I'm doing for now is using == for when === doesn't make sense. This is case-by-case. The problem with this is that any custom types defining a === method with no type restrictions may also hit this. I'm at least trying to cover the standard library types.

If you (or anyone else) has a better solution, I'd love to hear it!

from spectator.

postmodern avatar postmodern commented on May 27, 2024

@icy-arctic-fox got a little further.

Crystal code:

    @[AlwaysInline]
    def +(other : CharSet) : CharSet
      self | other
    end

Crystal spec:

  describe "#+" do
    mock Chars::CharSet do
      stub :|, other : Chars::CharSet
    end

    let(other) { described_class.new }
    let(return_value) { described_class.new }

    it "must call #+" do
      expect(subject).to receive(:|).with(other).and_return(return_value)

      expect(subject + other).to be(return_value)
    end
  end

Error:

In spec/char_set_spec.cr:658:1

 658 | it "must call #+" do
      ^
Error: expanding macro


There was a problem expanding macro 'it'

Called macro defined in macro 'define_example'

 17 | macro it(what = nil, *tags, **metadata, &block)

Which expanded to:

 >  1 |         
 >  2 |         
 >  3 | 
 >  4 |         _spectator_metadata(__temp_46, :metadata,  )
 >  5 |         _spectator_metadata(__temp_731, __temp_46,  )
 >  6 | 
 >  7 |         
 >  8 |           
 >  9 | 
 > 10 |           private def __temp_732() : Nil
 > 11 |             (expect(subject)).to(((receive(:|)).with(other)).and_return(return_value))
 > 12 | (expect(subject + other)).to(be(return_value))
 > 13 | 
 > 14 |           end
 > 15 | 
 > 16 |           ::Spectator::DSL::Builder.add_example(
 > 17 |             _spectator_example_name("must call #+"),
 > 18 |             ::Spectator::Location.new("/home/postmodern/code/crystal/chars.cr/spec/char_set_spec.cr", 658, 662),
 > 19 |             -> { new.as(::Spectator::Context) },
 > 20 |             __temp_731
 > 21 |           ) do |example|
 > 22 |             example.with_context(SpectatorTestContext::Group__temp_205::Group__temp_726) do
 > 23 |               
 > 24 |                 __temp_732
 > 25 |               
 > 26 |             end
 > 27 |           end
 > 28 | 
 > 29 |         
 > 30 |       
Error: instantiating 'Spectator::Example#with_context(SpectatorTestContext::Group__temp_205::Group__temp_726.class)'


In spec/char_set_spec.cr:658:1

 658 | it "must call #+" do
      ^
Error: expanding macro


There was a problem expanding macro 'it'

Called macro defined in macro 'define_example'

 17 | macro it(what = nil, *tags, **metadata, &block)

Which expanded to:

 >  1 |         
 >  2 |         
 >  3 | 
 >  4 |         _spectator_metadata(__temp_46, :metadata,  )
 >  5 |         _spectator_metadata(__temp_731, __temp_46,  )
 >  6 | 
 >  7 |         
 >  8 |           
 >  9 | 
 > 10 |           private def __temp_732() : Nil
 > 11 |             (expect(subject)).to(((receive(:|)).with(other)).and_return(return_value))
 > 12 | (expect(subject + other)).to(be(return_value))
 > 13 | 
 > 14 |           end
 > 15 | 
 > 16 |           ::Spectator::DSL::Builder.add_example(
 > 17 |             _spectator_example_name("must call #+"),
 > 18 |             ::Spectator::Location.new("/home/postmodern/code/crystal/chars.cr/spec/char_set_spec.cr", 658, 662),
 > 19 |             -> { new.as(::Spectator::Context) },
 > 20 |             __temp_731
 > 21 |           ) do |example|
 > 22 |             example.with_context(SpectatorTestContext::Group__temp_205::Group__temp_726) do
 > 23 |               
 > 24 |                 __temp_732
 > 25 |               
 > 26 |             end
 > 27 |           end
 > 28 | 
 > 29 |         
 > 30 |       
Error: instantiating '__temp_732()'


In spec/char_set_spec.cr:659:24

 659 | expect(subject).to receive(:|).with(other).and_return(return_value)
                        ^-
Error: instantiating 'Spectator::Expectation::Target(Chars::CharSet)#to(Spectator::ValueStub(Chars::CharSet))'


In lib/spectator/src/spectator/expectation.cr:104:7

 104 | def to(stub : Stub, message = nil) : Nil
       ^-
Error: The syntax `expect(...).to receive(...)` requires the expression passed to `expect` be stubbable (a mock or double)

from spectator.

postmodern avatar postmodern commented on May 27, 2024

Success! After adding the double mock statements it worked. I'm curious why are two mock statements required, in the specs and another within a let/subject?

from spectator.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.