Recommended resources
The freely available resources this lesson will direct you to.
Hello. Last lesson, you built a booking slice that creates a booking and retrieves it while the process is running. A manual request showed that the path works; it did not leave you a repeatable check for the next change. Today you will add two focused tests to your existing repository: one that creates and retrieves a booking, and one that checks a failure response. Aim to finish in about 45 minutes, using documentation for exact APIs but no generated test or implementation code.
Start with the contract, not a testing example
Spend the first 12 minutes in your repository. Open the API contract you wrote and note the status, headers, and JSON fields promised for a successful create, a successful get, and a get for an unknown ID. If your contract chose a different failure case, such as malformed create JSON, use that instead.
Try writing the tests before consulting the resources below. The successful test should send a create request through your actual router, obtain the returned ID, and use that ID in a get request through the same handler instance. The failure test should use a fresh handler and store, so its outcome does not depend on what another test created. If you stall, name the specific obstacle—for example, constructing a request, capturing a response, or decoding its body—before looking up the API.
Keep the tests beside the package whose HTTP behavior they check. You do not need a running process, a database, a mock repository, or a new testing framework. Use the real in-memory store from your baseline slice.
Exercise the route the client actually uses
The most useful boundary here is the assembled http.Handler that contains your routes, not a direct call to a create or get function. Sending requests through the router also checks route matching and path-ID extraction. If you call a handler function directly, a test may pass even though its route is incorrectly registered.
Watch the recorder-based portion of How To Test HTTP Handlers In Golang?! by Anthony GG. The example is simpler than your booking service; take the request-and-response testing technique, not its handler design.
How To Test HTTP Handlers In Golang?!
Watch How To Test HTTP Handlers In Golang?! by Anthony GG for an in-process way to invoke an HTTP handler and inspect its response.
Watch the recorder approach. Focus on how the request, recorder, handler call, and assertions fit together. Your test should invoke your assembled booking router rather than copy the video's example handler.
httptest.NewRequest makes an incoming server request; httptest.NewRecorder captures what your handler writes. After the handler returns, Result() gives you a response whose status, headers, and body you can inspect. This avoids a listening socket while still checking your HTTP contract.
Use the official net/http/httptest reference to resolve signatures and one important detail: inspect response headers from the result, not the recorder's deprecated HeaderMap.
httptest package - net/http/httptest - Go Packages
Read the Go standard-library documentation for the request and recorder APIs you need to test your existing handler.
Under Functions > NewRequest, start at “NewRequest wraps NewRequestWithContext using context.Background.” Read the request guidance through the end of that function's description. Under Types > ResponseRecorder, begin at “ResponseRecorder is an implementation of http.ResponseWriter” and read the recorder example and methods through the final Result note. Pay particular attention to NewRecorder, the status-code caveat on Code, and the header and body returned by Result().
If assembling the router currently happens only inside main, make the smallest change needed to let a test construct it with a fresh store. Keep that assembly boundary in the appropriate package; do not move booking logic into main merely to make the test compile.
Make two tests that would catch real regressions
A Go test file ends in _test.go, and test functions have the TestXxx(*testing.T) form. The testing package overview also distinguishes tests in the same package, which can access unexported identifiers, from external _test packages, which test through exported APIs. Choose whichever lets you check the assembled handler without distorting your production package boundaries.
testing package - testing - Go Packages
Read the Go standard-library overview to confirm how tests are discovered and where they can live.
In Documentation > Overview, begin at “Package testing provides support for automated testing of Go packages.” Read the test conventions through the explanation of same-package and external-package tests. Use testing.T to report mismatches with a useful “got; want” message.
For the successful create-and-get test, construct one fresh store and one router. Send a valid create request using the field names and values in your contract. Check the create status and any promised header, then decode the response sufficiently to obtain its ID. Assert that the ID is present rather than assuming the first booking will always receive a particular value. Send a get request for that ID through the same router. Check its status and the booking fields that matter: the returned ID and the client-supplied values should agree with the create request.
For the failure test, construct another fresh store and router. A get for an ID that has never been created should return the not-found status and response shape specified by your contract. Do not accept a successful status with an empty booking body. If malformed JSON was your chosen failure instead, assert its specified error response and ensure the test does not treat a partially populated booking as success.
Compare behavior, not incidental formatting. Decode JSON rather than comparing an entire raw JSON string, and avoid comparing the entire http.Response. Close response bodies after inspecting them. If a status or decoded ID is wrong, stop that test before using the invalid result in a later request; otherwise, one defect can produce several misleading failures.
These are small HTTP contract tests with a real in-memory store. They establish more than a store-only test would, without introducing network setup or test doubles. Resist expanding them into a full matrix of validation cases today; two trustworthy signals are the goal.
Check that the tests can fail
Run the whole repository's tests, then rerun without cached results if you are uncertain what executed:
go test ./...
go test ./... -count=1
Read failures against the contract before changing production code. A failed assertion may reveal a handler defect, an incorrect test expectation, or an assumption that was never recorded.
Finally, make one temporary, deliberate mismatch in a test expectation and confirm that go test fails for the reason you expect. Restore the expectation and rerun the suite. A green result matters only if the assertion could detect the behavior changing.
Record what you wrote unaided and any exact API you looked up. This is useful evidence of implementation fluency, not a coverage percentage: a test that checks the right response fields is more valuable here than a larger test that merely reaches many lines.
You now have a repeatable check for the baseline service's central promise and one failure response. The critical details are fresh state per test, a shared store across create and get within the successful test, requests sent through the real router, and assertions tied to your contract. Next, you will classify the external lookups you made—distinguishing an exact API reference from guidance or an outsourced implementation—so you can keep using tools without handing over the engineering work.