icy-arctic-fox / spectator

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

How to use macros in example descriptions? #10

Open postmodern opened 3 years ago

postmodern commented 3 years ago

I ran into a weird issue, where macros are not being properly expanded within the description text of describe or it blocks.

Example

require "./spec_helper"

Spectator.describe Test do
  {% var = :foo %}

  it "#{{ var.id }}" do
  end
end

Output

Test
  "#{{var.id}}"
postmodern commented 3 years ago

I've also tried \#{{ var.id }} and \##{{{ var.id }}}, can't seem to output #foo.

icy-arctic-fox commented 3 years ago

It doesn't look like it's possible to use a variable from a macro in a description like this. As far as I can tell, since it is it's own macro, it can't see var.

String interpolation wasn't supported/enabled at all for test names. That has been implemented, so the following is now possible:

Spectator.describe Test do
  var = :foo

  it "##{var}" do
  end
end

~This will be in the next patch update.~ Released in v0.9.24.

What are you trying to do that requires using a macro variable? Maybe there's another approach we can take.

postmodern commented 3 years ago

Trying to generate a bunch of describe blocks, with specs inside, using a list of id names and Class names. https://github.com/postmodern/v4l2.cr/blob/5f2e6882f510af86a3ff646f1321a9845ae21c9d/spec/device_spec.cr#L50-L64

With RSpec you'd either use a shared_example with input "parameters" or you'd just use {...}.each do |name,constant| with closures and meta-programming.

icy-arctic-fox commented 3 years ago

Using {{ var }} works fine, but it seems that it's only the case where it's prefixed with # that it has problems.

I have a bit of code that does similar to what you've done:

Spectator.describe Leaf::AnyNode do
  # Test the AnyNode class with all node types.
  {% for type_name in Leaf::NodeType.constants %}
    context "{{type_name.id}}Node" do
      let(node) { new_{{type_name.downcase}}_node }
      subject(any) { described_class.new(node) }

      describe "#node_type" do
        subject { any.node_type }

        it "returns {{type_name.id}}" do
          is_expected.to eq(Leaf::NodeType::{{type_name.id}})
        end
      end

# ...

For prefixing with #, it looks like there are workarounds: https://github.com/crystal-lang/crystal/issues/5287

GrantBirki commented 4 months ago

@icy-arctic-fox Hey there 👋!

Just stumbling across this issue as I recently started using crystal. I am trying to have it statements created inside of a loop and got pretty much the same error this issue describes:

There was a problem expanding macro '_spectator_metadata'

Code in macro 'it'

Here is a sample of my code:

describe ".cron?" do
  context "with valid cron strings" do
    crons.each do |value|
      it "returns true for '#{value}'" do
        expect(Common.cron?(value)).to be_true
      end
    end
  end

  context "with non-cron strings" do
    non_crons.each do |value|
      it "returns false for '#{value}'" do
        expect(Common.cron?(value)).to be_false
      end
    end
  end
end

Are there any work arounds to this?

icy-arctic-fox commented 4 months ago

@GrantBirki

In short, you can either use sample - https://github.com/icy-arctic-fox/spectator/wiki/Sample-Values or create a loop with a macro like here.

You may also be running into an issue like #41

The issue is that Spectator transforms it into a method:

it "works" do
  # ...
end

becomes

def it_works
  # ...
end

By doing it this way, loops effectively become:

(1..3).each do |i|
  def it_works
    # ...
  end
end

Which is not valid syntax. The other solutions available work around this issue.

It would be great to actually support the .each approach, but it would take a lot of effort to re-implement how tests and contexts are defined.