--- name: bdd-testing description: Behavior-Driven Development testing for dance-lessons-coach 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: dance-lessons-coach Team version: "1.0.0" based-on: pkg/bdd implementation --- # BDD Testing for dance-lessons-coach Behavior-Driven Development testing framework using Godog for the dance-lessons-coach 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 # 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