2015-09-08 1 views
1

Я пытаюсь получить покрытие 100% кода в этом простом файле обработчика http.Простой HTTP-обработчик, проверяющий все пути

Файл записывает заголовки ответа по умолчанию в случае успеха и затем возвращает 200 с помощью «Понга», который я тестировал ниже. Тем не менее, существует также вероятность того, что запись заголовков по умолчанию приведет к возникновению ошибки, и в этом случае ожидается ответ 500 с корпусом внутренней ошибки.

Я изо всех сил пытаюсь выяснить, как вызвать ответный ответ 500 в тесте. Случай потерпит неудачу, если по какой-либо причине второй параметр функции writeDefaultHeaders был изменен на «html», например, поскольку html не поддерживает тип содержимого ответа в моей службе.

Что такое идиоматический способ издеваться над этим вызовом/удалять эту ветвь ошибки в коде?

Спасибо.

ping_handler_test.go

package main 

import (
    "net/http" 
    "net/http/httptest" 
    "testing" 
) 

func Test200PingHandler(t *testing.T) { 
    req, _ := http.NewRequest("GET", "/ping", nil) 
    w := httptest.NewRecorder() 

    PingHandler(w, req) 

    if w.Code != http.StatusOK { 
     t.Errorf("Ping Handler Status Code is NOT 200; got %v", w.Code) 
    } 

    if w.Body.String() != "Pong" { 
     t.Errorf("Ping Handler Response Body is NOT Pong; got %v", w.Body.String()) 
    } 
} 

// This fails as it is the same setup as the passing success case 
func Test500PingHandler(t *testing.T) { 
    req, _ := http.NewRequest("GET", "/ping", nil) 
    w := httptest.NewRecorder() 

    PingHandler(w, req) 

    if w.Code != http.StatusInternalServerError { 
     t.Errorf("Ping Handler Status Code is NOT 500; got %v", w.Code) 
    } 

    if w.Body.String() != "Internal Server Error" { 
     t.Errorf("Ping Handler Response Body is NOT Internal Server Error; got %v", w.Body.String()) 
    } 
} 

func BenchmarkPingHandler(b *testing.B) { 
    for i := 0; i < b.N; i++ { 
     req, _ := http.NewRequest("GET", "/ping", nil) 
     w := httptest.NewRecorder() 

     PingHandler(w, req) 
    } 
} 

ping_handler.go

package main 

import (
    "fmt" 
    "net/http" 
) 

func PingHandler(w http.ResponseWriter, r *http.Request) { 
    err := writeDefaultHeaders(w, "text") 
    if err != nil { 
     handleException(w, err) 
     return 
    } 

    fmt.Fprintf(w, "Pong") 
} 

func writeDefaultHeaders(w http.ResponseWriter, contentType string) error { 
    w.Header().Set("X-Frame-Options", "DENY") 
    w.Header().Set("X-Content-Type-Options", "nosniff") 
    w.Header().Set("X-XSS-Protection", "1;mode=block") 

    switch contentType { 
    case "text": 
     w.Header().Set("Content-Type", "text/plain; charset=utf-8") 
     return nil 
    case "json": 
     w.Header().Set("Content-Type", "application/json; charset=UTF-8") 
     return nil 
    default: 
     return errors.New("Attempting to render an unknown content type") 
    } 
} 

Редактировать Другой пример:

json_response, err := json.Marshal(response) 
if err != nil { 
    handleException(w, err) 
    return 
} 

В этом случае, как я могу проверить json.Marshal, возвращающий ошибку?

+1

Пробовали ли вы просто с помощью 'PingHandler (ноль, ноль)'? Это должно вызвать ошибку, которую я предполагаю? У меня нет всего вашего кода, поэтому я не могу его запустить и проверить – user3591723

+1

Можете ли вы добавить содержимое 'writeDefaultHeaders'? Прямой (проверяемый) подход заключается в том, чтобы написать собственный тип, который включает 'http.ResponseWriter' и просто переопределяет метод (ы), который вы хотите протестировать. – elithrar

+0

@elithrar Добавил его в вопрос. – John

ответ

3

Что такое идиоматический способ издеваться над этим вызовом/ударом этой ветви ошибки в коде?

Обычно для тестирования вы хотите использовать открытый интерфейс и либо предоставить реализацию в код (NewMyThing(hw HeaderWriter)) или использовать какой-то другой механизм (например, в DefaultHeaderWriter, который вы можете обменять в тесте).

Поскольку этот код является частным, вы можете просто использовать переменную, хотя:

var writeDefaultHeaders = func(w http.ResponseWriter, contentType string) error { 
    w.Header().Set("X-Frame-Options", "DENY") 
    w.Header().Set("X-Content-Type-Options", "nosniff") 
    w.Header().Set("X-XSS-Protection", "1;mode=block") 

    switch contentType { 
    case "text": 
     w.Header().Set("Content-Type", "text/plain; charset=utf-8") 
     return nil 
    case "json": 
     w.Header().Set("Content-Type", "application/json; charset=UTF-8") 
     return nil 
    default: 
     return errors.New("Attempting to render an unknown content type") 
    } 
} 

func PingHandler(w http.ResponseWriter, r *http.Request) { 
    err := writeDefaultHeaders(w, "text") 
    if err != nil { 
     handleException(w, err) 
     return 
    } 

    fmt.Fprintf(w, "Pong") 
} 

и переставить в тесте:

func Test500PingHandler(t *testing.T) { 
    writeDefaultHeaders = headerWriterFunc(func(w http.ResponseWriter, contentType string) error { 
     return fmt.Errorf("ERROR") 
    }) 
    // ... 
} 

Вы, вероятно, хотите, чтобы установить его обратно, когда вы» сделанный.

На мой взгляд, замена одной функции, подобной этой, не является хорошей практикой тестирования. Тесты должны быть против публичного API, чтобы вы могли возиться с вашим кодом, не переписывая свои тесты каждый раз, когда вы делаете одно изменение.

пример

Интерфейс:

type Marshaler interface { 
    Marshal(v interface{}) ([]byte, error) 
} 

type jsonMarshaler struct{} 

func (_ *jsonMarshaler) Marshal(v interface{}) ([]byte, error) { 
    return json.Marshal(v) 
} 

var marshaler Marshaler = (*jsonMarshaler)(nil) 

И потом:

json_response, err := marshaler.Marshal(response) 
+0

Это в основном то, что я искал. Благодарю. Можете ли вы привести пример того, как будет работать интерфейс? Например, я сделал редактирование своего сообщения с вызовом stdlib, который может возвращать ошибку, которая вызывает запуск условного блока ошибки. – John

1

Если у меня не хватает чего-то, чтобы получить сообщение об ошибке - удалить hardcoded "text" и получить все, что вы передаете, поскольку contentType будет чем-то в запросе. Разберите его из запроса, а затем передайте его в writeDefaultHeaders. Передача дело либо "text" или "json", все остальное должно дать вам вашу ошибку, предполагая, что handleException работает, как ожидалось (вы не показали его)

Пример (конечно, вы не хотите «Content-Type» заголовок к выглядеть)

package main 

import (
    "net/http" 
    "net/http/httptest" 
    "testing" 
) 

func Test200PingHandler(t *testing.T) { 
    req, _ := http.NewRequest("GET", "/ping", nil) 
    req.Header().Set("Content-Type", "text") 
    //req.Header().Set("Content-Type", "json") 
    w := httptest.NewRecorder() 

    PingHandler(w, req) 

    if w.Code != http.StatusOK { 
     t.Errorf("Ping Handler Status Code is NOT 200; got %v", w.Code) 
    } 

    if w.Body.String() != "Pong" { 
     t.Errorf("Ping Handler Response Body is NOT Pong; got %v", w.Body.String()) 
    } 
} 

// This fails as it is the same setup as the passing success case 
func Test500PingHandler(t *testing.T) { 
    req, _ := http.NewRequest("GET", "/ping", nil) 
    req.Header().Set("Content-Type", "fail") 
    w := httptest.NewRecorder() 

    PingHandler(w, req) 

    if w.Code != http.StatusInternalServerError { 
     t.Errorf("Ping Handler Status Code is NOT 500; got %v", w.Code) 
    } 

    if w.Body.String() != "Internal Server Error" { 
     t.Errorf("Ping Handler Response Body is NOT Internal Server Error; got %v", w.Body.String()) 
    } 
} 

главный

package main 

import (
    "fmt" 
    "net/http" 
) 

func PingHandler(w http.ResponseWriter, r *http.Request) { 
    err := writeDefaultHeaders(w, req.Header().Get("Content-Type")) 
    if err != nil { 
     handleException(w, err) 
     return 
    } 

    fmt.Fprintf(w, "Pong") 
} 

func writeDefaultHeaders(w http.ResponseWriter, contentType string) error { 
    w.Header().Set("X-Frame-Options", "DENY") 
    w.Header().Set("X-Content-Type-Options", "nosniff") 
    w.Header().Set("X-XSS-Protection", "1;mode=block") 

    switch contentType { 
    case "text": 
     w.Header().Set("Content-Type", "text/plain; charset=utf-8") 
     return nil 
    case "json": 
     w.Header().Set("Content-Type", "application/json; charset=UTF-8") 
     return nil 
    default: 
     return errors.New("Attempting to render an unknown content type") 
    } 
} 
+0

Благодарим вас за ответ, но это не совсем то, на что я надеюсь. Вы правы, что я могу использовать Тип содержимого в запросе или Принять, чтобы следовать спецификации http, но я не хочу, чтобы тип ответа контролировался пользователем. Я иду за Ruby, где я надеюсь высмеять вызов функции writeDefaultHeaders, чтобы иметь неправильный входной параметр или что-то подобное. – John

+0

Тогда вам понадобится какая-то логика для определения «contentType». Это всего лишь пример использования заголовка Content-Type, потому что мне приходилось что-то придумывать. Если у вас есть правильный случай жесткого кодирования, вы никогда не получите отказ, это так просто ... – user3591723

0

Как вы написали его, этот код никогда не будет достигнуто в PingHandler:

потому что единственное место, где вы возвращаете ошибку, - это когда writeDefaultHeaders передается что-то другое, кроме текста или json, а в PingHandler вы используете «текст» жесткого кода, поэтому обработчик ping никогда не вызовет handleException, а обработка ошибок будет излишней. Нет другого места, где вы могли бы вернуть ошибку в writeDefaultHeaders.

Если вы хотите протестировать handleException, чтобы увидеть, что он корректно возвращает ошибку 500 (это то, что вы утверждаете/проверяете в Test500PingHandler), просто создайте функцию PingHandlerFail в тестовом файле, которая устанавливает неправильный тип ответа и использует это - нет другого способа вызвать код ошибки.

func PingHandlerFail(w http.ResponseWriter, r *http.Request) { 
    err := writeDefaultHeaders(w, "foo") 
    if err != nil { 
     handleException(w, err) 
     return 
    } 
    fmt.Fprintf(w, "Pong") 
} 

В качестве альтернативы, изменить PingHandler установить CONTENTTYPE на основе некоторых критериев запроса, как заканчивается ли запрос в .json или нет (что вы предположительно должны сделать для того, чтобы служить либо JSON или текст), так что вы можете инициировать ошибку как-то - в настоящее время, поскольку PingHandler никогда не служит ничем, кроме текста, код ошибки является избыточным, а результат - нецелесообразным.

Смежные вопросы