# BDD Testing Debugging Guide Comprehensive guide to debugging BDD tests for dance-lessons-coach. ## Common Issues and Solutions ### 1. "Undefined Step" Warnings **Symptoms:** ``` Feature: Greet Service Scenario: Default greeting # features/greet.feature:3 Given the server is running # ??? UNDEFINED STEP When I request the default greeting # ??? UNDEFINED STEP Then the response should be "..." # ??? UNDEFINED STEP ``` **Root Cause:** Step patterns don't match Godog's exact expectations. Godog is very particular about regex escaping. **Common Pattern Issues:** - `\"` vs `\\"` (single vs double escaping) - Exact quote handling in JSON patterns - Parameter capture group syntax **Debugging Steps:** 1. **Run with progress format:** ```bash godog --format=progress features/greet.feature ``` 2. **Check suggested patterns:** ``` You can implement step definitions for the undefined steps with these snippets: func theResponseShouldBe(arg1, arg2 string) error { return godog.ErrPending } func InitializeScenario(ctx *godog.ScenarioContext) { ctx.Step(`^the response should be "{\\"([^"]*)\\":\\"([^"]*)\\"}"$`, theResponseShouldBe) } ``` 3. **Compare with your implementation:** ```go // ❌ Wrong pattern (single escaping) ctx.Step(`^the response should be "{\"([^"]*)\":\"([^"]*)\"}"$`, sc.commonSteps.theResponseShouldBe) // ✅ Correct pattern (double escaping - matches Godog's suggestion) ctx.Step(`^the response should be "{\\"([^"]*)\\":\\"([^"]*)\\"}"$`, sc.commonSteps.theResponseShouldBe) ``` **Key Insight:** Godog expects `\\"` (four backslashes + quote) for escaped quotes in JSON patterns, not `\"` (two backslashes + quote). **Solution:** Use Godog's EXACT regex patterns, paying special attention to: - JSON escaping: `\\"` not `\"` - Parameter names: Use `arg1, arg2` as suggested - Capture groups: Match Godog's exact regex syntax ### 2. JSON Comparison Failures **Symptoms:** ``` Expected response body "{\"message\":\"Hello world!\"}", got "{\"message\":\"Hello world!\"}\n" ``` **Root Causes:** - Trailing newlines in JSON responses - Improper escaping in feature files - Quote handling issues **Debugging Steps:** 1. **Check actual response:** ```bash curl -v http://localhost:9191/api/v1/greet/ ``` 2. **Inspect in step implementation:** ```go func (sc *StepContext) theResponseShouldBe(expected string) error { fmt.Printf("Expected: %q\n", expected) fmt.Printf("Actual: %q\n", string(sc.client.lastBody)) // ... } ``` 3. **Verify feature file escaping:** ```gherkin # ❌ Wrong escaping Then the response should be "{"message":"Hello world!"}" # ✅ Correct escaping Then the response should be "{\\"message\\":\\"Hello world!\\"}" ``` **Solution:** Trim newlines and properly clean JSON: ```go cleanExpected := strings.Trim(expected, `"\`) actual := strings.TrimSuffix(string(body), "\n") ``` ### 3. Server Connection Issues **Symptoms:** ``` Request failed: dial tcp [::1]:9191: connect: connection refused ``` **Root Causes:** - Server not started - Port conflict - Server crashed during test **Debugging Steps:** 1. **Check server manually:** ```bash curl -v http://localhost:9191/api/ready ``` 2. **Check port usage:** ```bash lsof -i :9191 netstat -an | grep 9191 ``` 3. **Add debug logging to server startup:** ```go func (s *Server) Start() error { log.Info().Int("port", s.port).Msg("Starting test server") // ... log.Info().Str("url", s.baseURL).Msg("Test server started") return s.waitForServerReady() } ``` 4. **Verify test suite hooks:** ```go func InitializeTestSuite(ctx *godog.TestSuiteContext) { ctx.BeforeSuite(func() { log.Info().Msg("BeforeSuite: Starting shared server") sharedServer = testserver.NewServer() if err := sharedServer.Start(); err != nil { log.Error().Err(err).Msg("Failed to start server") panic(err) } log.Info().Msg("BeforeSuite: Server started successfully") }) // ... } ``` **Solution:** Ensure server starts before tests and check for port conflicts. ### 4. Context Type Mismatches **Symptoms:** ``` cannot use ctx (type *godog.ScenarioContext) as type context.Context in argument to InitializeScenario ``` **Root Cause:** Mixing `context.Context` with `*godog.ScenarioContext`. **Debugging Steps:** 1. **Check function signatures:** ```go // ❌ Wrong func InitializeScenario(ctx context.Context) { // Wrong type! // ... } // ✅ Correct func InitializeScenario(ctx *godog.ScenarioContext) { // ... } ``` 2. **Verify step registration:** ```go // ✅ Correct func InitializeAllSteps(ctx *godog.ScenarioContext, client *testserver.Client) { sc := NewStepContext(client) ctx.Step(`^the server is running$`, sc.theServerIsRunning) // ... } ``` **Solution:** Always use `*godog.ScenarioContext` for step registration. ### 5. Step Not Executing **Symptoms:** Step is defined but doesn't seem to execute. **Root Causes:** - Step pattern doesn't match - Step not registered - Context not passed correctly **Debugging Steps:** 1. **Add logging to step:** ```go func (sc *StepContext) theServerIsRunning() error { log.Info().Msg("theServerIsRunning step executing") return sc.client.Request("GET", "/api/ready", nil) } ``` 2. **Verify registration:** ```go func InitializeScenario(ctx *godog.ScenarioContext) { client := testserver.NewClient(sharedServer) steps.InitializeAllSteps(ctx, client) // Verify steps are registered log.Info().Int("steps", len(ctx.Steps())).Msg("Steps registered") for _, step := range ctx.Steps() { log.Debug().Str("pattern", step.Pattern).Msg("Registered step") } } ``` 3. **Check Godog output:** ```bash godog --format=progress --show-step-definitions ``` **Solution:** Ensure proper registration and pattern matching. ## Advanced Debugging Techniques ### 1. Verbose Logging Add detailed logging to all components: ```go // pkg/bdd/steps/steps.go func (sc *StepContext) theServerIsRunning() error { log.Info().Msg("=== theServerIsRunning step started ===") err := sc.client.Request("GET", "/api/ready", nil) if err != nil { log.Error().Err(err).Msg("Server verification failed") } else { log.Info().Msg("Server verification succeeded") } log.Info().Msg("=== theServerIsRunning step completed ===") return err } ``` ### 2. HTTP Request Tracing Add request/response logging: ```go // pkg/bdd/testserver/client.go func (c *Client) Request(method, path string, body []byte) error { url := c.server.GetBaseURL() + path log.Debug().Str("method", method).Str("url", url).Msg("Sending request") req, err := http.NewRequest(method, url, nil) if err != nil { log.Error().Err(err).Msg("Request creation failed") return fmt.Errorf("failed to create request: %w", err) } resp, err := http.DefaultClient.Do(req) if err != nil { log.Error().Err(err).Msg("Request failed") return fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() c.lastResp = resp c.lastBody, err = io.ReadAll(resp.Body) if err != nil { log.Error().Err(err).Msg("Response read failed") } else { log.Debug().Int("status", resp.StatusCode). Str("body", string(c.lastBody)). Msg("Received response") } return err } ``` ### 3. Test Execution Tracing Run tests with detailed output: ```bash # Verbose Godog output godog --format=pretty --verbose features/greet.feature # Go test with verbose output go test ./features/... -v # Show step definitions godog --format=progress --show-step-definitions ``` ### 4. Interactive Debugging Use `dlv` for interactive debugging: ```bash # Install Delve go install github.com/go-delve/delve/cmd/dlv@latest # Start debugging dlv test ./features/... # Set breakpoints (b) pkg/bdd/steps/steps.go:25 # Continue execution (c) # Print variables (p) sc.client.lastBody ``` ### 5. Network Debugging Capture HTTP traffic: ```bash # Use mitmproxy mitmproxy --mode reverse:http://localhost:9191 --listen-port 9192 # Configure client to use proxy client := &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyURL(url.Parse("http://localhost:9192")), }, } ``` ## Common Error Patterns ### Pattern 1: JSON Escaping Issues **Error:** ``` Expected: "{\"message\":\"Hello world!\"}" Got: "{\"message\":\"Hello world!\"}" ``` **Solution:** Properly escape in feature files and clean in code. ### Pattern 2: Trailing Newlines **Error:** ``` Expected: "..." Got: "...\n" ``` **Solution:** `strings.TrimSuffix(actual, "\n")` ### Pattern 3: Port Conflicts **Error:** ``` listen tcp :9191: bind: address already in use ``` **Solution:** ```bash # Find and kill process kill -9 $(lsof -ti :9191) ``` ### Pattern 4: Server Not Ready **Error:** ``` server did not become ready after 30 attempts ``` **Solution:** 1. Check server logs 2. Increase timeout in `waitForServerReady` 3. Verify configuration ### Pattern 5: Step Registration Issues **Error:** ``` panic: step definition for "the server is running" already exists ``` **Solution:** Ensure steps are registered only once per context. ## Debugging Checklist ### ✅ Pre-Test Checklist - [ ] Server port (9191) is available - [ ] No zombie test processes running - [ ] Feature files use proper JSON escaping - [ ] Step patterns match Godog's exact suggestions - [ ] All steps are properly registered - [ ] Context types are correct ### ✅ Runtime Checklist - [ ] Server starts successfully (check logs) - [ ] Readiness endpoint responds (curl localhost:9191/api/ready) - [ ] Steps execute in correct order - [ ] HTTP requests succeed - [ ] Responses match expectations - [ ] No undefined step warnings ### ✅ Post-Test Checklist - [ ] Server shuts down gracefully - [ ] All resources are cleaned up - [ ] Port is released - [ ] No goroutine leaks - [ ] Test results are consistent ## Debugging Tools ### Essential Tools | Tool | Purpose | Installation | |------|---------|--------------| | `curl` | HTTP requests | Built-in | | `godog` | BDD test runner | `go install github.com/cucumber/godog/cmd/godog@latest` | | `dlv` | Go debugger | `go install github.com/go-delve/delve/cmd/dlv@latest` | | `mitmproxy` | HTTP proxy | `brew install mitmproxy` | | `jq` | JSON processing | `brew install jq` | ### Useful Commands ```bash # Check server health curl -v http://localhost:9191/api/health # Test specific endpoint curl -v http://localhost:9191/api/v1/greet/John # Check port usage lsof -i :9191 # Kill process on port kill -9 $(lsof -ti :9191) # Run specific feature godog features/greet.feature -v # Show step definitions godog --format=progress --show-step-definitions # Debug with Delve dlv test ./features/... ``` ## Performance Debugging ### Slow Test Execution **Symptoms:** Tests take longer than expected. **Debugging Steps:** 1. **Profile test execution:** ```bash go test ./features/... -cpuprofile=cpu.prof go tool pprof cpu.prof ``` 2. **Identify bottlenecks:** ``` (pprof) top (pprof) web ``` 3. **Common bottlenecks:** - Server startup time - HTTP request/response - JSON parsing - Step execution **Optimizations:** - Reuse HTTP connections - Enable parallel execution - Reduce logging in tests - Cache configuration ### Memory Issues **Symptoms:** High memory usage during tests. **Debugging Steps:** 1. **Memory profiling:** ```bash go test ./features/... -memprofile=mem.prof go tool pprof mem.prof ``` 2. **Check for leaks:** ``` (pprof) top (pprof) inuse_objects ``` 3. **Common memory issues:** - Unclosed response bodies - Goroutine leaks - Cached data not released - Large JSON responses **Solutions:** - Ensure all `resp.Body.Close()` calls - Clean up resources in AfterScenario - Limit response sizes in tests - Use streaming for large data ## CI/CD Debugging ### Failed CI Builds **Common Issues:** - Port conflicts in parallel builds - Missing dependencies - Environment differences - Timeout issues **Debugging Steps:** 1. **Check CI logs:** ```yaml - name: Run BDD tests run: | set -x go test ./features/... -v 2>&1 | tee test-output.txt exit ${PIPESTATUS[0]} ``` 2. **Add debug information:** ```yaml - name: Show environment run: | echo "Go version: $(go version)" echo "Working directory: $(pwd)" echo "Port 9191 status: $(lsof -i :9191 || echo 'available')" echo "Feature files: $(find features -name '*.feature')" ``` 3. **Common CI fixes:** ```yaml # Use unique ports for parallel jobs env: BDD_PORT: ${{ 9191 + github.run_id % 100 }} # Increase timeouts - name: Run tests with timeout timeout-minutes: 5 run: go test ./features/... -timeout=5m ``` ## Debugging Workflow ### Systematic Debugging Approach 1. **Reproduce the issue:** ```bash go test ./features/... -v ``` 2. **Isolate the problem:** - Run specific feature - Run specific scenario - Disable other tests 3. **Gather information:** - Logs - HTTP responses - Step execution order - Timing information 4. **Formulate hypothesis:** - What might be causing the issue? - Where could the problem be? 5. **Test hypothesis:** - Add logging - Modify test - Check assumptions 6. **Implement fix:** - Update code - Add validation - Improve error handling 7. **Verify fix:** - Run tests again - Check related scenarios - Test edge cases 8. **Document solution:** - Update debugging guide - Add to gotchas section - Improve error messages ## Common Fixes ### Fix 1: JSON Escaping **Before:** ```gherkin Then the response should be "{"message":"Hello world!"}" ``` **After:** ```gherkin Then the response should be "{\\"message\\":\\"Hello world!\\"}" ``` ### Fix 2: Step Pattern **Before:** ```go ctx.Step(`^I request greeting "(.*)"$`, sc.iRequestAGreetingFor) ``` **After:** ```go ctx.Step(`^I request a greeting for "([^"]*)"$`, sc.iRequestAGreetingFor) ``` ### Fix 3: Response Cleaning **Before:** ```go if string(c.lastBody) != expected { return fmt.Errorf("mismatch") } ``` **After:** ```go actual := strings.TrimSuffix(string(c.lastBody), "\n") if actual != expected { return fmt.Errorf("expected %q, got %q", expected, actual) } ``` ### Fix 4: Server Verification **Before:** ```go func (sc *StepContext) theServerIsRunning() error { // Assume server is running return nil } ``` **After:** ```go func (sc *StepContext) theServerIsRunning() error { // Actually verify server is running return sc.client.Request("GET", "/api/ready", nil) } ``` ## Success Stories ### Case Study 1: Undefined Steps **Problem:** Tests passed but showed undefined step warnings. **Debugging:** 1. Ran `godog --format=progress` 2. Compared patterns with implementation 3. Found slight regex mismatch **Solution:** Updated step patterns to match Godog's exact suggestions. **Result:** ✅ No more undefined step warnings. ### Case Study 2: JSON Mismatch **Problem:** Response validation failed despite correct JSON. **Debugging:** 1. Added logging to see actual vs expected 2. Found trailing newline in response 3. Discovered improper escaping in feature file **Solution:** Added newline trimming and proper JSON cleaning. **Result:** ✅ All JSON comparisons now pass. ### Case Study 3: Server Connection **Problem:** Intermittent connection refused errors. **Debugging:** 1. Added server readiness logging 2. Found race condition in server startup 3. Discovered port conflict in CI **Solution:** Improved readiness verification and added port conflict detection. **Result:** ✅ Reliable server startup in all environments. ## Final Tips 1. **Start simple**: Test one scenario at a time 2. **Add logging**: You can never have too much debug info 3. **Verify assumptions**: Don't assume anything works 4. **Test manually**: Use curl to verify endpoints 5. **Read logs**: They often contain the answer 6. **Check patterns**: Godog is particular about regex 7. **Clean data**: Trim newlines, escape JSON properly 8. **Validate early**: Catch issues before they multiply 9. **Document fixes**: Help future you (and others) 10. **Ask for help**: Sometimes a fresh perspective helps ## Conclusion BDD testing debugging follows a systematic approach: 1. **Identify** the specific issue 2. **Isolate** the problematic component 3. **Gather** relevant information 4. **Analyze** the root cause 5. **Implement** the fix 6. **Verify** the solution 7. **Document** the learning With this guide and the patterns established in our implementation, you should be able to debug any BDD testing issue efficiently.