Recording My Crystal Snippets from Today’s Learning

I want to document some snippets from today’s learning while working on open-source projects. 0. Printing Available Methods for an Object of a Class I found it useful to debug an object’s methods in a way similar to Ruby. Here’s a snippet that helped me with this: # Print the available methods of a Crystal class # Usage: `puts Spec::CLI.methods.sort` class Object macro methods {{ @type.methods.map &.name.stringify }} end end puts Spec::CLI.methods.sort # => ["abort!", "add_formatter", ...] More macro examples can be found in the Crystal Macro Methods documentation. 1. Filtering Crystal Spec Tests Based on Tags I worked on executing different types of tests, including unit and integration tests. There are multiple approaches to separating them, and many ideas can be found in this Crystal Forum discussion. Here’s the approach I took: Project Folder Structure My project’s test structure looks like this: $ tree spec spec ├── awscr-s3 ├── awscr-s3_spec.cr ├── fixtures.cr ├── integration │   ├── compose.yml │   └── minio_spec.cr └── spec_helper.cr The integration tests are marked with the tag "integration". Filtering Tests Based on Tags One of the things I love about Crystal is that the code is simple and intuitive to read. While learning about the Spec module in the Crystal Spec Documentation, I found links to the source code, which helped me understand how filtering works. Here’s how I implemented tag-based filtering: # spec/spec_helper.cr class Spec::CLI def tags @tags end end Spec.around_each do |example| tags = Spec.cli.tags # By default, skip tagged tests and run only unit tests next if (tags.nil? || tags.empty?) && !example.example.all_tags.empty? example.run end Explanation Spec.cli is a command-line interface that parses options and stores them internally in the @tags variable. For example, when running: $ crystal spec --tag 'integration' The "integration" tag is stored as a Set in @tags. This allows me to check which filters were enabled without manually parsing the command-line arguments. However, there’s a small drawback: @tags is not publicly accessible. To work around this, I extended the Spec::CLI class and exposed it. (There may be a better way to do this.) The second part of the code is a simple filtering mechanism implemented using Spec.around_each: It checks the provided tags and then validates the test’s tags. If no tags are specified, all tagged tests are skipped by default. A simple debug statement like pp! example can help explore more filtering options. 2. Configuring Test Dependencies Based on Tags Integration tests allow sending real requests. Instead of adding tags to every integration test individually, we can leverage the folder structure (e.g., placing them in an integration folder). Here’s one way to configure WebMock dynamically based on test tags or file location: Spec.around_each do |example| integration = example.example.all_tags.includes?("integration") || example.example.file.includes?("spec/integration") WebMock.reset WebMock.allow_net_connect = integration example.run end That’s all for today!

Mar 15, 2025 - 13:21
 0
Recording My Crystal Snippets from Today’s Learning

I want to document some snippets from today’s learning while working on open-source projects.

0. Printing Available Methods for an Object of a Class

I found it useful to debug an object’s methods in a way similar to Ruby. Here’s a snippet that helped me with this:

# Print the available methods of a Crystal class
# Usage: `puts Spec::CLI.methods.sort`
class Object
  macro methods
    {{ @type.methods.map &.name.stringify }}
  end
end

puts Spec::CLI.methods.sort # => ["abort!", "add_formatter", ...]

More macro examples can be found in the Crystal Macro Methods documentation.

phantasy crystal spec

1. Filtering Crystal Spec Tests Based on Tags

I worked on executing different types of tests, including unit and integration tests.

There are multiple approaches to separating them, and many ideas can be found in this Crystal Forum discussion.

Here’s the approach I took:

Project Folder Structure

My project’s test structure looks like this:

$ tree spec   
spec
├── awscr-s3
├── awscr-s3_spec.cr
├── fixtures.cr
├── integration
│   ├── compose.yml
│   └── minio_spec.cr
└── spec_helper.cr

The integration tests are marked with the tag "integration".

Filtering Tests Based on Tags

One of the things I love about Crystal is that the code is simple and intuitive to read.

While learning about the Spec module in the Crystal Spec Documentation, I found links to the source code, which helped me understand how filtering works.

Here’s how I implemented tag-based filtering:

# spec/spec_helper.cr
class Spec::CLI
  def tags
    @tags
  end
end

Spec.around_each do |example|
  tags = Spec.cli.tags
  # By default, skip tagged tests and run only unit tests
  next if (tags.nil? || tags.empty?) && !example.example.all_tags.empty?
  example.run
end

Explanation

Spec.cli is a command-line interface that parses options and stores them internally in the @tags variable.

For example, when running:

$ crystal spec --tag 'integration'

The "integration" tag is stored as a Set in @tags. This allows me to check which filters were enabled without manually parsing the command-line arguments.

However, there’s a small drawback: @tags is not publicly accessible. To work around this, I extended the Spec::CLI class and exposed it. (There may be a better way to do this.)

The second part of the code is a simple filtering mechanism implemented using Spec.around_each:

  • It checks the provided tags and then validates the test’s tags.
  • If no tags are specified, all tagged tests are skipped by default.

A simple debug statement like pp! example can help explore more filtering options.

2. Configuring Test Dependencies Based on Tags

Integration tests allow sending real requests.

Instead of adding tags to every integration test individually, we can leverage the folder structure (e.g., placing them in an integration folder).

Here’s one way to configure WebMock dynamically based on test tags or file location:

Spec.around_each do |example|
  integration = example.example.all_tags.includes?("integration") || example.example.file.includes?("spec/integration")
  WebMock.reset

  WebMock.allow_net_connect = integration
  example.run
end

That’s all for today!