
Why Metadata Matters
Imagine you’re building a Learning Management System (LMS). You have tests for user registration, course creation, and grading. Some tests might be quick, while others might involve complex database interactions. You want to run only the tests you need, when you need them. This is where metadata comes in.
Metadata is like adding labels or tags to your tests. These labels provide extra information about the test, allowing you to control how and when they run. It’s a powerful way to organize and manage your tests, making them faster, more reliable, and easier to use.
Key Benefits of Using Metadata:
- Run Only What You Need: Focus on specific tests, saving time and resources.
- Isolate Failures: Quickly identify and fix problems by running only the failing tests.
- Avoid Unnecessary Setup: Skip expensive setup code when it’s not required.
- Customize Test Behavior: Change how tests run based on their metadata.
Understanding Metadata

Think of metadata as a hidden dictionary (or hash) attached to each of your tests. This dictionary stores information about the test, such as:
- Example Configuration: Whether the test is skipped, pending, or focused.
- Source Code Location: The file and line number where the test is defined.
- Status of Previous Runs: Whether the test passed, failed, or is pending.
- Specific Requirements: Information like which database or browser to use.
RSpec automatically adds some default metadata, and you can add your own custom metadata.
RSpec’s Default Metadata
Let’s see what RSpec automatically adds to the metadata.
require 'rspec'
require 'pp' # Pretty print for easier reading
RSpec.describe 'Course' do
it 'has a name' do |example|
pp example.metadata
end
end
Explanation:
require 'rspec'
andrequire 'pp'
: We load the RSpec testing library and the pretty print library.RSpec.describe 'Course'
: This starts a test group for the Course class.it 'has a name' do |example|
: This defines a single test case. The|example|
part is important. It allows us to access the current test’s metadata.pp example.metadata
: This prints the metadata hash to the console.
If you run this code, you’ll see a lot of information. Here are some key entries:
:description
: The string you passed to it (e.g., “has a name”).:full_description
: The combined description from describe and it (e.g., “Course has a name”).:described_class
: The class passed to the outermost describe block (e.g., Course).:file_path
: The path to the file containing the test.:example_group
: Metadata from the enclosing describe block.:last_run_status
: The status of the test from the last run (e.g., “unknown”).
Adding Custom Metadata
You can add your own metadata to tests. This is where the real power of metadata comes in.
RSpec.describe 'Student' do
it 'can enroll in a course', fast: true, database: :test do |example|
pp example.metadata
end
it 'can view grades', :slow do |example|
pp example.metadata
end
end
Explanation:
fast: true, database: :test
: We’ve added two custom metadata entries to the first test.:slow
: This is a shortcut for:slow => true
.
Now, if you run this code, you’ll see the custom metadata in the output.
Metadata Inheritance
Metadata can be inherited. If you add metadata to a describe
block, all the tests inside it will inherit that metadata.
RSpec.describe 'Assignment', database: :test do
it 'can be created' do |example|
pp example.metadata
end
context 'when graded' do
it 'has a score' do |example|
pp example.metadata
end
end
end
Explanation:
database: :test
is added to the describe block.- Both tests inside the describe block will inherit this metadata.
Derived Metadata
You can automatically add metadata to tests based on certain conditions using config.define_derived_metadata
.
RSpec.configure do |config|
config.define_derived_metadata(file_path: /spec\/models/) do |meta|
meta[:model_test] = true
end
end
RSpec.describe 'Course', file_path: 'spec/models/course_spec.rb' do
it 'has a title' do |example|
pp example.metadata
end
end
Explanation:
config.define_derived_metadata(file_path: /spec\/models/)
: This tells RSpec to add metadata to tests in files matching the regular expression/spec\/models/
.meta[:model_test] = true
: This adds the:model_test
metadata to the matching tests.
Default Metadata
You can set default metadata for all tests, but allow individual tests to override it.
RSpec.configure do |config|
config.define_derived_metadata do |meta|
meta[:aggregate_failures] = true unless meta.key?(:aggregate_failures)
end
end
RSpec.describe 'Quiz' do
it 'has multiple questions' do |example|
pp example.metadata
end
it 'can be graded', aggregate_failures: false do |example|
pp example.metadata
end
end
Explanation:
meta[:aggregate_failures] = true unless meta.key?(:aggregate_failures)
: This sets:aggregate_failures
totrue
by default, but only if it’s not already set.- The second test explicitly sets
:aggregate_failures
tofalse
, overriding the default.
Reading Metadata
You’ve already seen how to access metadata using the example argument in it blocks. You can also access it in hooks and let
declarations.
RSpec.configure do |config|
config.around(:example, :database) do |example|
puts "Starting test: #{example.metadata[:full_description]}"
example.run
puts "Ending test: #{example.metadata[:full_description]}"
end
end
RSpec.describe 'Enrollment' do
let(:enrollment_service) do |example|
EnrollmentService.new(database: example.metadata[:database])
end
it 'creates a new enrollment', database: :test do
# ...
end
end
Explanation:
config.around(:example, :database)
: This hook runs around tests with the:database
metadata.let(:enrollment_service) do |example|
: Thislet
declaration accesses the metadata.
Selecting Which Specs to Run
You can use metadata to filter which tests to run.
Excluding Examples
RSpec.configure do |config|
config.filter_run_excluding :slow
end
This will exclude all tests with the :slow
metadata.
Including Examples
RSpec.configure do |config|
config.filter_run_including :fast
end
This will only run tests with the :fast
metadata.
Command Line
You can also filter tests from the command line:
rspec --tag fast
rspec --tag '~slow' # Exclude slow tests
rspec --tag database:test # Run tests with database: :test
Sharing Code Conditionally
You can use metadata to share code conditionally using hooks, modules, and shared contexts.
RSpec.configure do |config|
config.before(:example, :database) do
DB.connect
end
config.after(:example, :database) do
DB.disconnect
end
end
This will only run the database connection code for tests with the :database
metadata.
Changing How Your Specs Run
Metadata can change how RSpec behaves.
:aggregate_failures
: Runs all expectations in a test, even if one fails.:pending
: Marks a test as expected to fail.:skip
: Skips a test entirely.:order
: Sets the order in which tests run.
RSpec.describe 'Grade' do
it 'calculates the average', aggregate_failures: true do
expect(1 + 1).to eq(3)
expect(2 + 2).to eq(5)
end
it 'is pending', pending: 'Waiting for API' do
# ...
end
it 'is skipped', skip: 'Not implemented yet' do
# ...
end
end