Есть ли способ стереть данные в автоответчике? - PullRequest
0 голосов
/ 10 марта 2019

Я пишу тест для промежуточного программного обеспечения эластичного поиска, где я использую функцию для создания тестовых серверов, в которой я передаю часть структур конфигурации для каждого теста, а в функции-обработчике они повторяются, и ожидаемый ответ записывается в Автор ответов. Это моя функция.

func newMockClient(url string) (*elasticsearch, error) {
    client, err := elastic.NewSimpleClient(elastic.SetURL(url))
    if err != nil {
        return nil, fmt.Errorf("error while initializing elastic client: %v", err)
    }
    es := &elasticsearch{
        url:    url,
        client: client,
    }
    return es, nil
}

type ServerSetup struct {
    Method, Path, Body, Response string
    HTTPStatus                   int
}

func buildTestServer(t *testing.T, setups []*ServerSetup) *httptest.Server {
    handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        requestBytes, _ := ioutil.ReadAll(r.Body)
        requestBody := string(requestBytes)

        matched := false
        for _, setup := range setups {
            if r.Method == setup.Method && r.URL.EscapedPath() == setup.Path {
                matched = true
                if setup.HTTPStatus == 0 {
                    w.WriteHeader(http.StatusOK)
                } else {
                    w.WriteHeader(setup.HTTPStatus)
                }
                _, err := w.Write([]byte(setup.Response))
                if err != nil {
                    t.Fatalf("Unable to write test server response: %v", err)
                }
            }
        }

        if !matched {
            t.Fatalf("No requests matched setup. Got method %s, Path %s, body %s", r.Method, r.URL.EscapedPath(), requestBody)
        }
    })

    return httptest.NewServer(handlerFunc)
}

Копируется с github.com/github/vulcanizer. Когда я запускаю один тест с использованием этого, он работает нормально. Например, этот тест

func TestCreateIndex(t *testing.T) {
    setup := &ServerSetup{
        Method:   "PUT",
        Path:     "/test",
        Response: `{"acknowledged": true, "shards_acknowledged": true, "index": "test"}`,
    }

    ts := buildTestServer(t, []*ServerSetup{setup})

    es, _ := newMockClient(ts.URL)

    err := es.createIndex(context.Background(), "test", nil)
    if err != nil {
        t.Fatalf("Index creation failed with error: %v\n", err)
    }

}

Но когда я пытаюсь проверить различные варианты поведения в одном тесте, подобном этому, я получаю ошибку http: multiple response.WriteHeader calls

func TestDeleteIndex(t *testing.T) {
    setup := &ServerSetup{
        Method:   "DELETE",
        Path:     "/test",
        Response: `{"acknowledged": true}`,
    }

    errSetup := &ServerSetup{
        Method:   "DELETE",
        Path:     "/test",
        Response: `{"acknowledged": false}`,
    }

    ctx := context.Background()

    ts := buildTestServer(t, []*ServerSetup{setup, errSetup})
    defer ts.Close()

    es, _ := newMockClient(ts.URL)

    err := es.deleteIndex(ctx, "test")
    if err != nil {
        t.Fatalf("Index creation failed with error: %v\n", err)
    }

    err = es.deleteIndex(ctx, "test")
    if err == nil {
        t.Fatal("Expected error but not found")
    }
}

Я предполагаю, что это из-за того факта, что когда я запускаю deleteIndex во второй раз, он снова отправляет эхо-запрос на сервер, но средство записи ответов уже было записано, поэтому он больше ничего не может записать в него.

Есть ли в любом случае проверка в начале функции моего обработчика, например

handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    if w != nil{
        // clear data in response writer
    }

// .........



}

Ответы [ 2 ]

3 голосов
/ 10 марта 2019

Я не думаю, что вы делаете правильный способ проверить свою функциональность. Вам нужно разделить свой тест на тестовые случаи для проверки различных типов поведения, таких как:

func Test_DeleteIndex(t *testing.T) {
    t.Run("Should be ok with correct setup", func(t *testing.T) {
        setup := &ServerSetup{
            Method:   "DELETE",
            Path:     "/test",
            Response: `{"acknowledged": true}`,
        }
        ctx := context.Background()
        ts := buildTestServer(t, []*ServerSetup{setup})
        defer ts.Close()
        es, _ := newMockClient(ts.URL)
        err := es.deleteIndex(ctx, "test")
        require.NoError(t, err)
    })

    t.Run("Shouldn't be ok with wrong setup", func(t *testing.T) {
        setup := &ServerSetup{
            Method:   "DELETE",
            Path:     "/test",
            Response: `{"acknowledged": false}`,
        }
        ctx := context.Background()
        ts := buildTestServer(t, []*ServerSetup{setup})
        defer ts.Close()
        es, _ := newMockClient(ts.URL)
        err := es.deleteIndex(ctx, "test")
        require.Error(t, err)
    })
}
2 голосов
/ 10 марта 2019

Проблема здесь в том, что для каждого запроса, который получает тестовый сервер, обработчик проходит по всем структурам ServerSetup, проверяя совпадения на основе метода и пути, но не прерывается в цикле при обнаружении совпадения.

Итак, во втором тестовом примере, поскольку вы передаете две структуры установки с одинаковыми Method и Path, два варианта установки будут соответствовать входящему запросу на DELETE /test, и программа попытается вызвать WriteHeader на ResponseWriter дважды.

Есть два способа решения этой проблемы:

Вариант 1

Если выЕсли вы хотите, чтобы сервер по-разному реагировал на последовательные вызовы одной и той же комбинации метода и пути, вы можете добавить атрибут, чтобы проверить, использовался ли уже экземпляр ServerSetup, и избежать каких-либо уже использованных структур установки.

Например:

type ServerSetup struct {
    Method, Path, Body, Response    string
    HTTPStatus                      int
    HasBeenCalled                   bool
}

func buildTestServer(t *testing.T, setups []*ServerSetup) *httptest.Server {
    handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        requestBytes, _ := ioutil.ReadAll(r.Body)
        requestBody := string(requestBytes)

        matched := false
        for _, setup := range setups {
            if setup.HasBeenCalled {
                continue // skip those that have already been called
            }
            if r.Method == setup.Method && r.URL.EscapedPath() == setup.Path {
                setup.HasBeenCalled = true
                matched = true
                if setup.HTTPStatus == 0 {
                    w.WriteHeader(http.StatusOK)
                } else {
                    w.WriteHeader(setup.HTTPStatus)
                }
                _, err := w.Write([]byte(setup.Response))
                if err != nil {
                    t.Fatalf("Unable to write test server response: %v", err)
                }
            }
            if matched {
                break // stop checking for matches if already found match
            }
        }

        if !matched {
            t.Fatalf("No requests matched setup. Got method %s, Path %s, body %s", r.Method, r.URL.EscapedPath(), requestBody)
        }
    })

    return httptest.NewServer(handlerFunc)
}

Вариант 2

Несколько более простым способом решения этой проблемы было бы создание отдельных тестовых серверов для каждого из этих двух вариантов.ses, по одному для каждой setup структуры, так как они включают разные результаты из одной и той же комбинации метода и пути.Проще говоря, вы можете разделить их на два отдельных теста.

Таким образом, вы получите:

func TestDeleteIndex_Success(t *testing.T) {
    setup := &ServerSetup{
        Method:   "DELETE",
        Path:     "/test",
        Response: `{"acknowledged": true}`,
    }

    ctx := context.Background()

    ts := buildTestServer(t, []*ServerSetup{setup})
    defer ts.Close()

    es, _ := newMockClient(ts.URL)

    err := es.deleteIndex(ctx, "test")
    if err != nil {
        t.Fatalf("Index creation failed with error: %v\n", err)
    }
}

func TestDeleteIndex_Error(t *testing.T) {
    errSetup := &ServerSetup{
        Method:   "DELETE",
        Path:     "/test",
        Response: `{"acknowledged": false}`,
    }

    ctx := context.Background()

    ts := buildTestServer(t, []*ServerSetup{errSetup})
    defer ts.Close()

    es, _ := newMockClient(ts.URL)

    err := es.deleteIndex(ctx, "test")
    if err == nil {
        t.Fatal("Expected error but not found")
    }
}

В будущем вам также следует избегать прохождения нескольких структур ServerSetup с одинаковымиметод-путь в будущем, чтобы избежать этой ошибки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...