Home > Software design >  Create a deadlineExceededError for unit tests with timeout: true
Create a deadlineExceededError for unit tests with timeout: true

Time:11-22

I am trying to create a unit test in my project where I mock the http client and setup the response that the client has to return. I need such behaviour because my code needs to behave accordingly in case the http client fails due to timeout: hence I need to mock the http client to return a deadlineExceededError and make a unit test out of it.

What I tried so far was to mock the client Do function in such a way that client.Do returns:

GetDoFunc = func(*http.Request) (*http.Response, error) {
    return nil, &url.Error{
        Op:  "Post",
        Err: context.DeadlineExceeded,
    }
}

it works but not fully, meaning that when I execute the code with such mocked behaviour, the kind of error returned is:

error(*net/url.Error) *{Op: "Post", URL: "", Err: error(context.deadlineExceededError) {}}

which again is correct, but not fully. Why? Because if I run the code and a real timeout happens I get something more complete:

error(*net/url.Error) *{Op: "Post", URL: "http://localhost:4500/scan/", Err: error(*net/http.httpError) *{err: "context deadline exceeded (Client.Timeout exceeded while awaiting headers)", timeout: true}}

what interests me the most is that timeout: true. If I manage to tell my mock to return it, I could assert that, which I find it more complete than asserting only that the returned error is of type deadlineExceededError.

CodePudding user response:

To not over-complicated the test too much, I'll suggest you this approach. First, start by defining your error:

type timeoutError struct {
    err     string
    timeout bool
}

func (e *timeoutError) Error() string {
    return e.err
}

func (e *timeoutError) Timeout() bool {
    return e.timeout
}

In this way, timeoutError implements both the Error() and Timeout interfaces.
Then you've to define the mock for the HTTP client:

type mockClient struct{}

func (m *mockClient) Do(req *http.Request) (*http.Response, error) {
    return nil, &timeoutError{
        err:     "context deadline exceeded (Client.Timeout exceeded while awaiting headers)",
        timeout: true,
    }
}

This simply returns the error defined above and nil as the http.Response. Lastly, let's see how you can write a sample unit test:

func TestSlowServer(t *testing.T) {
    r := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
    client := &mockClient{}

    _, err := client.Do(r)

    fmt.Println(err.Error())
}

If you debug this test and pause with the debugger on the err variable, you'll see the wanted result.
Thanks to this approach you can achieve what you need without bringing in any extra complexity. Let me know if works for you!

  • Related