---
name: bdd-testing
description: Behavior-Driven Development testing for DanceLessonsCoach using Godog. Use when creating or running BDD tests, implementing new features with BDD, or validating API endpoints through Gherkin scenarios.
license: MIT
metadata:
  author: DanceLessonsCoach Team
  version: "1.0.0"
  based-on: pkg/bdd implementation
---

# BDD Testing for DanceLessonsCoach

Behavior-Driven Development testing framework using Godog for the DanceLessonsCoach project. This skill provides comprehensive guidance for creating, running, and maintaining BDD tests that validate API endpoints and system behavior.

## Key Concepts

### Black Box Testing Principles
- **External API Only**: Tests interact only through public HTTP endpoints
- **No Internal Access**: No direct access to database, services, or internal components
- **Real HTTP Requests**: Actual network calls to verify system behavior
- **Isolation**: Each scenario runs with fresh client instances

### Hybrid In-Process Testing
- **Real Server Code**: Uses actual server implementation running in-test process
- **Fixed Port**: Test server runs on port 9191
- **No External Processes**: Avoids complex process management
- **Graceful Shutdown**: Proper server lifecycle management

## Commands

### Run BDD Tests

```bash
go test ./features/...
```

Runs all BDD tests in the features directory using Godog test runner.

**Arguments:**
- None (uses standard Go test infrastructure)

### Validate BDD Tests

```bash
./scripts/run-bdd-tests.sh
```

Validates BDD tests and fails if any undefined, pending, or skipped steps are found.

**Arguments:**
- None

### Create New Feature

```bash
# Create new feature file
 touch features/<feature_name>.feature

# Add Gherkin scenarios
# Implement step definitions in pkg/bdd/steps/
```

**Arguments:**
- `feature_name` - Name of the feature (e.g., "greet", "health")

## Workflows

### Implementing a New BDD Feature

1. **Create Feature File**: Define scenarios in Gherkin syntax
2. **Implement Steps**: Add step definitions following Godog's exact patterns
3. **Run Tests**: Execute and debug scenarios
4. **Validate**: Ensure no undefined/pending steps
5. **Document**: Add feature documentation

### Debugging BDD Tests

1. **Check Step Patterns**: Ensure steps match Godog's exact regex patterns
2. **Verify Server**: Confirm test server is running on port 9191
3. **Inspect Responses**: Check actual vs expected API responses
4. **Review Logs**: Examine test output for undefined steps
5. **Validate JSON**: Ensure proper JSON escaping in feature files

## Usage Examples

### Creating a Greet Feature

```gherkin
# features/greet.feature
Feature: Greet Service
  The greet service should return appropriate greetings

  Scenario: Default greeting
    Given the server is running
    When I request the default greeting
    Then the response should be "{\"message\":\"Hello world!\"}"

  Scenario: Personalized greeting
    Given the server is running
    When I request a greeting for "John"
    Then the response should be "{\"message\":\"Hello John!\"}"
```

### Creating a Health Feature

```gherkin
# features/health.feature
Feature: Health Endpoint
  The health endpoint should indicate server status

  Scenario: Health check returns healthy status
    Given the server is running
    When I request the health endpoint
    Then the response should be "{\"status\":\"healthy\"}"
```

### Implementing Step Definitions

```go
// pkg/bdd/steps/steps.go
func (sc *StepContext) theServerIsRunning() error {
    // Actually verify the server is running by checking the readiness endpoint
    return sc.client.Request("GET", "/api/ready", nil)
}

func (sc *StepContext) iRequestAGreetingFor(name string) error {
    return sc.client.Request("GET", fmt.Sprintf("/api/v1/greet/%s", name), nil)
}

func (sc *StepContext) iRequestTheDefaultGreeting() error {
    return sc.client.Request("GET", "/api/v1/greet/", nil)
}

func (sc *StepContext) iRequestTheHealthEndpoint() error {
    return sc.client.Request("GET", "/api/health", nil)
}

func (sc *StepContext) theResponseShouldBe(arg1, arg2 string) error {
    // The regex captures the full JSON from the feature file, including quotes
    // We need to extract just the key and value without the surrounding quotes and backslashes
    
    // Remove the surrounding quotes and backslashes
    cleanArg1 := strings.Trim(arg1, `"\`)
    cleanArg2 := strings.Trim(arg2, `"\`)
    
    // Build the expected JSON string
    expected := fmt.Sprintf(`{"%s":"%s"}`, cleanArg1, cleanArg2)
    
    return sc.client.ExpectResponseBody(expected)
}
```

### Registering Steps

```go
// pkg/bdd/steps/steps.go
func InitializeAllSteps(ctx *godog.ScenarioContext, client *testserver.Client) {
    sc := NewStepContext(client)

    // Use Godog's EXACT regex patterns and parameter names
    ctx.Step(`^I request a greeting for "([^"]*)"$`, sc.iRequestAGreetingFor)
    ctx.Step(`^I request the default greeting$`, sc.iRequestTheDefaultGreeting)
    ctx.Step(`^I request the health endpoint$`, sc.iRequestTheHealthEndpoint)
    ctx.Step(`^the response should be "{\"([^"]*)\":\"([^"]*)"}"$`, sc.theResponseShouldBe)
    ctx.Step(`^the server is running$`, sc.theServerIsRunning)
}
```

## Gotchas

### Step Pattern Matching
- **Use Godog's Exact Patterns**: Step regex must match Godog's suggestions precisely
- **Use Exact Parameter Names**: Godog expects `arg1, arg2`, not descriptive names
- **Avoid Undefined Warnings**: Even small deviations cause "undefined step" warnings
- **Test Patterns First**: Use `godog.ErrPending` to verify patterns work before implementing logic
- **Don't Over-Optimize Regex**: Use the patterns Godog provides, even if they seem verbose

### Critical Requirements from Validated Implementation

1. **Godog has very specific requirements** for step pattern matching:
   - Use the **exact regex pattern** that Godog suggests in error messages
   - Use the **exact parameter names** that Godog suggests (`arg1, arg2`, etc.)
   - Match the feature file syntax **exactly** including quotes and JSON formatting

2. **The "undefined" warnings are not a Godog bug** - they occur when step definitions don't match Godog's expected patterns exactly:
   - Using different regex patterns than what Godog suggests
   - Using descriptive parameter names instead of `arg1, arg2`
   - Not escaping quotes properly in JSON patterns
   - Trying to be "clever" with regex optimization

3. **Solution**: Always use the exact pattern and parameter names that Godog suggests in its error messages.

### JSON Escaping
- **Feature Files**: Use double backslashes for quotes: `"{\\"message\\":\\"Hello\\"}"`
- **Step Implementation**: Trim surrounding quotes and backslashes from captured groups
- **Response Validation**: Trim trailing newlines from JSON responses

### Server Verification
- **Actual HTTP Requests**: `theServerIsRunning` must make real HTTP call to `/api/ready`
- **No Mocking**: Black box testing requires real server verification
- **Port Conflicts**: Test server runs on fixed port 9191

### Context Handling
- **ScenarioContext vs Context**: Steps receive `*godog.ScenarioContext`, not `context.Context`
- **Client Access**: Store client in StepContext struct for step access
- **Fresh Instances**: Each scenario gets new client instance

## Best Practices

### Step Definition Patterns

```go
// ✅ DO: Use Godog's exact regex patterns and parameter names
ctx.Step(`^I request a greeting for "([^"]*)"$`, sc.iRequestAGreetingFor)
ctx.Step(`^the response should be "{\"([^"]*)\":\"([^"]*)"}"$`, sc.theResponseShouldBe)

// ❌ DON'T: Use different parameter names or patterns
ctx.Step(`^I request greeting "(.*)"$`, sc.iRequestAGreetingFor)  // Wrong pattern
ctx.Step(`^the response should be "{\"message\":\"([^"]*)"}"$`, sc.theResponseShouldBe)  // Wrong pattern
```

### Validated Step Definition Strategy

1. **First eliminate "undefined" warnings** by using Godog's exact suggested patterns
2. **Return `godog.ErrPending`** initially to confirm pattern matching works
3. **Then implement actual validation** logic
4. **One pattern per step type** - Use generic patterns to cover similar steps

### Response Validation

```go
// ✅ DO: Trim newlines and properly unescape JSON
func (c *Client) ExpectResponseBody(expected string) error {
    actual := strings.TrimSuffix(string(c.lastBody), "\n")
    if actual != expected {
        return fmt.Errorf("expected %q, got %q", expected, actual)
    }
    return nil
}

// ❌ DON'T: Assume exact string matching without cleanup
func (c *Client) ExpectResponseBody(expected string) error {
    if string(c.lastBody) != expected {  // May fail due to newlines
        return fmt.Errorf("mismatch")
    }
}
```

### Test Server Management

```go
// ✅ DO: Use hybrid in-process testing
func (s *Server) Start() error {
    // Start real server in same process
    go func() {
        if err := s.httpServer.ListenAndServe(); err != nil {
            log.Error().Err(err).Msg("Test server failed")
        }
    }()
    return s.waitForServerReady()
}

// ❌ DON'T: Use external process management
func startServer() {
    // Avoid: cmd := exec.Command("go", "run", "./cmd/server")
}
```

### Response Validation

```go
// ✅ DO: Trim newlines and properly unescape JSON
func (c *Client) ExpectResponseBody(expected string) error {
    actual := strings.TrimSuffix(string(c.lastBody), "\n")
    if actual != expected {
        return fmt.Errorf("expected %q, got %q", expected, actual)
    }
    return nil
}

// ❌ DON'T: Assume exact string matching without cleanup
func (c *Client) ExpectResponseBody(expected string) error {
    if string(c.lastBody) != expected {  // May fail due to newlines
        return fmt.Errorf("mismatch")
    }
}
```

## Progressive Disclosure

### Core Instructions (SKILL.md)
- BDD testing fundamentals
- Common workflows and patterns
- Gotchas and best practices
- Basic troubleshooting

### Detailed Reference (references/)
- **GODOG_PATTERNS.md**: Advanced step pattern examples
- **TEST_SERVER.md**: Test server implementation details
- **DEBUGGING.md**: Advanced debugging techniques
- **EXAMPLES.md**: Complete feature examples

### Scripts (scripts/)
- **run-bdd-tests.sh**: Test validation and execution
- **debug-steps.sh**: Step pattern debugging
- **generate-stubs.sh**: Step definition stub generation

## Validation

### Test Validation Script

```bash
# scripts/run-bdd-tests.sh
#!/bin/bash
set -e

echo "Running BDD tests..."
go test ./features/... -v

# Fail if any undefined/pending/skipped steps
echo "Validating test results..."
if go test ./features/... 2>&1 | grep -q "undefined\|pending\|skipped"; then
    echo "ERROR: Found undefined, pending, or skipped steps"
    exit 1
fi

echo "✓ All BDD tests passed with no undefined steps"
```

### Common Validation Issues

| Issue | Cause | Solution |
|-------|-------|----------|
| Undefined steps | Step pattern doesn't match Godog's exact regex | Use Godog's suggested pattern |
| JSON mismatch | Trailing newlines or improper escaping | Trim newlines, properly unescape JSON |
| Server not running | Test server failed to start | Check port 9191, verify server logs |
| Context errors | Wrong context type passed to steps | Use `*godog.ScenarioContext`, not `context.Context` |

## References

- [Godog Documentation](https://github.com/cucumber/godog)
- [Gherkin Reference](https://cucumber.io/docs/gherkin/)
- [BDD Best Practices](references/BDD_BEST_PRACTICES.md)
- [Test Server Implementation](references/TEST_SERVER.md)
- [Debugging Guide](references/DEBUGGING.md)

## Troubleshooting

### "Undefined Step" Warnings

**Symptoms:** Tests pass but show "undefined step" warnings

**Cause:** Step regex doesn't match Godog's exact pattern suggestions

**Solution:**
1. Run `godog --format=progress` to see suggested patterns
2. Update step registration to use exact patterns
3. Ensure function names match step descriptions

### JSON Comparison Failures

**Symptoms:** Response validation fails despite correct JSON

**Cause:** Trailing newlines or improper escaping in feature files

**Solution:**
1. Trim newlines: `strings.TrimSuffix(response, "\n")`
2. Properly escape JSON in feature files: `"{\\"key\\":\\"value\\"}"`
3. Trim quotes in step implementation: `strings.Trim(arg, `"\`)`

### Server Connection Errors

**Symptoms:** "connection refused" or server not responding

**Cause:** Test server not running or port conflict

**Solution:**
1. Verify server on port 9191: `curl http://localhost:9191/api/ready`
2. Check server logs for startup errors
3. Ensure no other process using port 9191

### Context Type Mismatches

**Symptoms:** Compilation errors about context types

**Cause:** Passing wrong context type to step functions

**Solution:**
1. Store `*godog.ScenarioContext` in StepContext
2. Use stored context for step registration
3. Access client through StepContext struct

## Assets

- **feature-template.feature**: Gherkin feature file template
- **step-template.go**: Go step definition template
- **test-server-template.go**: Test server implementation template
- **validation-script.sh**: Test validation script template