WebsiteRacer
which takes two URLs and "races" them by hitting them with an HTTP GET and returning the URL which returned first. If none of them return within 10 seconds then it should return an error
.net/http
to make the HTTP calls.net/http/httptest
to help us test them.select
to synchronise processes../racer_test.go:14:9: undefined: Racer
racer_test.go:25: got '', want 'http://www.quii.co.uk'
time.Now()
to record just before we try and get the URL
.http.Get
to try and get the contents of the URL
. This function returns an http.Response
and an error
but so far we are not interested in these values.time.Since
takes the start time and returns a time.Duration
of the difference.net/http/httptest
where you can easily create a mock HTTP server.httptest.NewServer
takes an http.HandlerFunc
which we are sending in via an anonymous function.http.HandlerFunc
is a type that looks like this: type HandlerFunc func(ResponseWriter, *Request)
.ResponseWriter
and a Request
, which is not too surprising for an HTTP server.httptest.NewServer
which makes it easier to use with testing, as it finds an open port to listen on and then you can close it when you're done with your test.time.Sleep
when we get a request to make it slower than the other one. Both servers then write an OK
response with w.WriteHeader(http.StatusOK)
back to the caller.Racer
code a lot easier to read.makeDelayedServer
to move some uninteresting code out of the test and reduce repetition.defer
defer
it will now call that function at the end of the containing function.select
which helps us synchronise processes really easily and clearly.ping
ping
which creates a chan struct{}
and returns it.struct{}
and not another type like a bool
? Well, a chan struct{}
is the smallest data type available from a memory perspective so we get no allocation versus a bool
. Since we are closing and not sending anything on the chan, why allocate anything?http.Get(url)
.make
when creating a channel; rather than say var ch chan struct{}
. When you use var
the variable will be initialised with the "zero" value of the type. So for string
it is ""
, int
it is 0, etc.nil
and if you try and send to it with <-
it will block forever because you cannot send to nil
channelsselect
myVar := <-ch
. This is a blocking call, as you're waiting for a value.select
lets you do is wait on multiple channels. The first one to send a value "wins" and the code underneath the case
is executed.ping
in our select
to set up two channels for each of our URL
s. Whichever one writes to its channel first will have its code executed in the select
, which results in its URL
being returned (and being the winner).Racer
takes longer than 10 seconds.Racer
to return two values now, the winning URL (which we ignore in this test with _
) and an error
../racer_test.go:37:10: assignment mismatch: 2 variables but 1 values
Racer
to return the winner and an error
. Return nil
for our happy cases.got, _ := Racer(slowURL, fastURL)
, knowing that we should check we don't get an error in our happy scenario.time.After
is a very handy function when using select
. Although it didn't happen in our case you can potentially write code that blocks forever if the channels you're listening on never return a value. time.After
returns a chan
(like ping
) and will send a signal down it after the amount of time you define.a
or b
manage to return they win, but if we get to 10 seconds then our time.After
will send a signal and we'll return an error
.Racer
(which uses ConfigurableRacer
under the hood) and our sad path test can use ConfigurableRacer
.error
.select
time.After
in one of your cases
to prevent your system blocking forever.httptest
net/http
servers which is consistent and less for you to learn.