# Hooks
Cucumber for Elixir provides hooks that allow you to run code before and after scenarios. This is useful for setup and teardown operations like database transactions, authentication, or any other cross-cutting concerns.
## Overview
Hooks are defined in support files placed in `test/features/support/` and are automatically discovered and executed at the appropriate times during test execution.
## Defining Hooks
To define hooks, create a module that uses `Cucumber.Hooks`:
```elixir
# test/features/support/database_support.exs
defmodule DatabaseSupport do
use Cucumber.Hooks
# Global hook - runs before every scenario
before_scenario context do
# Your setup code here
{:ok, Map.put(context, :setup_done, true)}
end
# Tagged hook - only runs for scenarios with @database tag
before_scenario "@database", context do
:ok = Ecto.Adapters.SQL.Sandbox.checkout(MyApp.Repo)
if context.async do
Ecto.Adapters.SQL.Sandbox.mode(MyApp.Repo, {:shared, self()})
end
{:ok, context}
end
# After hooks run in reverse order of definition
after_scenario _context do
# Cleanup code
:ok
end
end
```
## Hook Types
### Before Scenario Hooks
Run before each scenario:
```elixir
# Global before hook
before_scenario context do
# Runs before every scenario
{:ok, context}
end
# Tagged before hook
before_scenario "@slow", context do
# Only runs for scenarios tagged with @slow
{:ok, Map.put(context, :timeout, 30_000)}
end
```
### After Scenario Hooks
Run after each scenario:
```elixir
# Global after hook
after_scenario context do
# Runs after every scenario
:ok
end
# Tagged after hook
after_scenario "@api", context do
# Only runs for scenarios tagged with @api
# Clean up API state
:ok
end
```
## Return Values
Hooks support the same return values as step definitions:
- `:ok` - Keeps the context unchanged
- `{:ok, map}` - Merges the map into the context
- `%{} = map` - Merges the map into the context
- `{:error, reason}` - Fails the scenario before it starts
## Hook Execution Order
1. Before hooks run in the order they are defined
2. After hooks run in reverse order (last defined runs first)
3. Tagged hooks only run for scenarios with matching tags
4. Global hooks run for all scenarios
## Tag Inheritance
Feature-level tags are inherited by all scenarios in that feature:
```gherkin
@database
Feature: User Management
# All scenarios inherit @database tag
Scenario: Create user
# This scenario has @database tag
Given a new user
@api
Scenario: API user creation
# This scenario has both @database and @api tags
Given an API request
```
## Context Variables
The context passed to hooks includes:
- `:scenario_name` - The name of the current scenario
- `:async` - Whether the feature is running in async mode
- `:step_history` - List of steps executed (empty in before hooks)
- Any data added by previous hooks or steps
## Practical Examples
### Database Setup
```elixir
defmodule DatabaseSupport do
use Cucumber.Hooks
before_scenario "@database", context do
:ok = Ecto.Adapters.SQL.Sandbox.checkout(MyApp.Repo)
if context.async do
Ecto.Adapters.SQL.Sandbox.mode(MyApp.Repo, {:shared, self()})
end
{:ok, context}
end
end
```
### Authentication
```elixir
defmodule AuthSupport do
use Cucumber.Hooks
before_scenario "@authenticated", context do
user = MyApp.Factory.insert(:user)
token = MyApp.Auth.generate_token(user)
{:ok, Map.merge(context, %{
current_user: user,
auth_token: token
})}
end
end
```
### Performance Monitoring
```elixir
defmodule PerformanceSupport do
use Cucumber.Hooks
before_scenario "@performance", context do
start_time = System.monotonic_time()
{:ok, Map.put(context, :start_time, start_time)}
end
after_scenario "@performance", context do
duration = System.monotonic_time() - context.start_time
milliseconds = System.convert_time_unit(duration, :native, :millisecond)
IO.puts("Scenario completed in #{milliseconds}ms")
:ok
end
end
```
## Configuration
By default, support files are loaded from `test/features/support/**/*.exs`. You can customize this in your config:
```elixir
# config/test.exs
config :cucumber,
support: ["test/support/**/*.exs", "test/cucumber_support/**/*.exs"]
```
## Best Practices
1. **Keep hooks focused** - Each hook should have a single responsibility
2. **Use tags wisely** - Don't create too many specialized hooks
3. **Avoid side effects** - Hooks should be predictable and repeatable
4. **Clean up in after hooks** - Ensure proper cleanup even if scenarios fail
5. **Use context passing** - Share data between hooks and steps via context
## Troubleshooting
### Hooks not running
1. Ensure your support files are in the correct directory
2. Verify the module uses `Cucumber.Hooks`
3. Check that tags match exactly (including the @ symbol)
4. Confirm the file has a `.exs` extension
### Hook execution order
Remember that:
- Before hooks run in definition order
- After hooks run in reverse definition order
- Tagged hooks only run when tags match