···86868787The single root test pattern allows for efficient resource management where setup costs can be amortized across multiple related test cases.
88888989+## Interactive Component Testing
9090+9191+Interactive components that use `fmt.Scanf` for user input require special testing infrastructure to prevent tests from hanging while waiting for stdin.
9292+9393+### Testing Success Scenarios
9494+9595+Interactive handlers should test both success and error paths:
9696+9797+- **Valid user selections** - User chooses valid menu options
9898+- **Cancellation** - User chooses to cancel (option 0)
9999+- **Invalid choices** - User selects out-of-range options
100100+- **Empty results** - Search returns no results
101101+- **Network errors** - Service calls fail
102102+103103+This ensures tests run reliably in automated environments while maintaining coverage of the non-interactive code paths.
104104+89105## Errors
9010691107Error coverage follows a systematic approach to identify and test failure scenarios:
···951113. **Resource Exhaustion** - Database connection failures, memory limits
961124. **Constraint Violations** - Duplicate keys, foreign key failures
971135. **State Validation** - Testing functions with invalid system states
114114+6. **Interactive Input** - Invalid user choices, cancellation handling, input simulation errors
+15-2
internal/handlers/books.go
···33import (
44 "context"
55 "fmt"
66+ "io"
67 "os"
78 "slices"
89 "strconv"
···2122 config *store.Config
2223 repos *repo.Repositories
2324 service *services.BookService
2525+ reader io.Reader
2426}
25272628// NewBookHandler creates a new book handler
···5254 return fmt.Errorf("failed to close service: %w", err)
5355 }
5456 return h.db.Close()
5757+}
5858+5959+// SetInputReader sets the input reader
6060+func (h *BookHandler) SetInputReader(reader io.Reader) {
6161+ h.reader = reader
5562}
56635764func (h *BookHandler) printBook(book *models.Book) {
···142149 fmt.Print("\nEnter number to add (1-", len(results), "), or 0 to cancel: ")
143150144151 var choice int
145145- if _, err := fmt.Scanf("%d", &choice); err != nil {
146146- return fmt.Errorf("invalid input")
152152+ if h.reader != nil {
153153+ if _, err := fmt.Fscanf(h.reader, "%d", &choice); err != nil {
154154+ return fmt.Errorf("invalid input")
155155+ }
156156+ } else {
157157+ if _, err := fmt.Scanf("%d", &choice); err != nil {
158158+ return fmt.Errorf("invalid input")
159159+ }
147160 }
148161149162 if choice == 0 {