测试Go http.Request.FormFile?

Nat*_*and 4 go

在尝试测试端点时如何设置Request.FormFile?

部分代码:

func (a *EP) Endpoint(w http.ResponseWriter, r *http.Request) {
    ...

    x, err := strconv.Atoi(r.FormValue("x"))
    if err != nil {
        a.ren.Text(w, http.StatusInternalServerError, err.Error())
        return
    }

    f, fh, err := r.FormFile("y")
    if err != nil {
        a.ren.Text(w, http.StatusInternalServerError, err.Error())
        return
    }
    defer f.Close()
    ...
}
Run Code Online (Sandbox Code Playgroud)

如何使用httptest lib生成具有我在FormFile中可以获得的值的发布请求?

Fed*_*ico 8

您不需要按照其他答案的建议模拟完整的 FormFile 结构。该mime/multipart包实现了一个 Writer 类型,可让您创建一个 FormFile。从文档

CreateFormFile 是一个围绕 CreatePart 的便捷包装器。它使用提供的字段名和文件名创建一个新的表单数据头。

func (w *Writer) CreateFormFile(fieldname, filename string) (io.Writer, error)
Run Code Online (Sandbox Code Playgroud)

然后,您可以将此 io.Writer 传递给httptest.NewRequest,它接受读者作为参数。

request := httptest.NewRequest("POST", "/", myReader)
Run Code Online (Sandbox Code Playgroud)

为此,您可以将 FormFile 写入 io.ReaderWriter 缓冲区或使用 io.Pipe。这是一个使用管道的完整示例:

func TestUploadImage(t *testing.T) {
    //Set up a pipe to avoid buffering
    pr, pw := io.Pipe()
    //This writers is going to transform 
    //what we pass to it to multipart form data
    //and write it to our io.Pipe
    writer := multipart.NewWriter(pw)

    go func() {
        defer writer.Close()
        //we create the form data field 'fileupload'
        //wich returns another writer to write the actual file 
        part, err := writer.CreateFormFile("fileupload", "someimg.png")
        if err != nil {
            t.Error(err)
        }

        //https://yourbasic.org/golang/create-image/
        img := createImage()

        //Encode() takes an io.Writer.
        //We pass the multipart field
        //'fileupload' that we defined
        //earlier which, in turn, writes
        //to our io.Pipe
        err = png.Encode(part, img)
        if err != nil {
            t.Error(err)
        }
    }()

    //We read from the pipe which receives data
    //from the multipart writer, which, in turn,
    //receives data from png.Encode().
    //We have 3 chained writers !
    request := httptest.NewRequest("POST", "/", pr)
    request.Header.Add("Content-Type", writer.FormDataContentType())

    response := httptest.NewRecorder()
    handler := UploadFileHandler()
    handler.ServeHTTP(response, request)

    t.Log("It should respond with an HTTP status code of 200")
    if response.Code != 200 {
        t.Errorf("Expected %s, received %d", 200, response.Code)
    }
    t.Log("It should create a file named 'someimg.png' in uploads folder")
    if _, err := os.Stat("./uploads/someimg.png"); os.IsNotExist(err) {
        t.Error("Expected file ./uploads/someimg.png' to exist")
    }
}
Run Code Online (Sandbox Code Playgroud)

此函数利用image包动态生成文件,利用您可以传递io.Writerpng.Encode. 同样,您可以通过多部分 Writer 生成 CSV 格式的字节(包“encoding/csv”中的 NewWriter),即时生成文件,而无需从文件系统中读取任何内容。


Wil*_*lem 7

我将这些和其他答案合并到一个没有管道或 goroutine 的Echo示例中:

func Test_submitFile(t *testing.T) {
    path := "testfile.txt"

    body := new(bytes.Buffer)
    writer := multipart.NewWriter(body)
    part, err := writer.CreateFormFile("object", path)
    assert.NoError(t, err)
    sample, err := os.Open(path)
    assert.NoError(t, err)

    _, err = io.Copy(part, sample)
    assert.NoError(t, err)
    assert.NoError(t, writer.Close())

    e := echo.New()
    req := httptest.NewRequest(http.MethodPost, "/", body)
    req.Header.Set(echo.HeaderContentType, writer.FormDataContentType()) 
    rec := httptest.NewRecorder()
    c := e.NewContext(req, rec)
    c.SetPath("/submit")
    if assert.NoError(t, submitFile(c)) {
        assert.Equal(t, 200, rec.Code)
        assert.Contains(t, rec.Body.String(), path)
        fi, err := os.Stat(expectedPath)
        if os.IsNotExist(err) {
            t.Fatal("Upload file does not exist", expectedPath)
        }
        assert.Equal(t, wantSize, fi.Size())
    }
}
Run Code Online (Sandbox Code Playgroud)


Eam*_*voy 6

如果你看一下FormFile函数的实现,你会看到它读取了暴露的MultipartForm字段.

https://golang.org/src/net/http/request.go?s=39022:39107#L1249

        // FormFile returns the first file for the provided form key.
  1258  // FormFile calls ParseMultipartForm and ParseForm if necessary.
  1259  func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error) {
  1260      if r.MultipartForm == multipartByReader {
  1261          return nil, nil, errors.New("http: multipart handled by MultipartReader")
  1262      }
  1263      if r.MultipartForm == nil {
  1264          err := r.ParseMultipartForm(defaultMaxMemory)
  1265          if err != nil {
  1266              return nil, nil, err
  1267          }
  1268      }
  1269      if r.MultipartForm != nil && r.MultipartForm.File != nil {
  1270          if fhs := r.MultipartForm.File[key]; len(fhs) > 0 {
  1271              f, err := fhs[0].Open()
  1272              return f, fhs[0], err
  1273          }
  1274      }
  1275      return nil, nil, ErrMissingFile
  1276  }
Run Code Online (Sandbox Code Playgroud)

在您的测试中,您应该能够创建测试实例multipart.Form并将其分配给您的请求对象 - https://golang.org/pkg/mime/multipart/#Form

type Form struct {
        Value map[string][]string
        File  map[string][]*FileHeader
}
Run Code Online (Sandbox Code Playgroud)

当然,这将要求您使用真正的文件路径,从测试的角度来看,这不是很好.要解决这个问题,您可以定义一个接口来FormFile从请求对象中读取并将模拟实现传递给您的EPstruct.

这是一篇很好的文章,其中包含一些如何执行此操作的示例:https://husobee.github.io/golang/testing/unit-test/2015/06/08/golang-unit-testing.html


Man*_*seh 6

通过结合以前的答案,这对我有用:

    filePath := "file.jpg"
    fieldName := "file"
    body := new(bytes.Buffer)

    mw := multipart.NewWriter(body)

    file, err := os.Open(filePath)
    if err != nil {
        t.Fatal(err)
    }

    w, err := mw.CreateFormFile(fieldName, filePath)
    if err != nil {
        t.Fatal(err)
    }

    if _, err := io.Copy(w, file); err != nil {
        t.Fatal(err)
    }

    // close the writer before making the request
    mw.Close()

    req := httptest.NewRequest(http.MethodPost, "/upload", body)
    
    req.Header.Add("Content-Type", mw.FormDataContentType())

    res := httptest.NewRecorder()

    // router is of type http.Handler
    router.ServeHTTP(res, req)
Run Code Online (Sandbox Code Playgroud)