The last article gave you an introduction to Golang unit testing, and next, how to Mock. A Mock is a way of simulating some context to create a particular condition that you want to test to see if your logic works correctly under that particular condition.

I won’t talk too much about the comparison of Golang’s Mock test environments with Java and other languages, but I’ll get straight to the bottom of it (I’ve attached my own Demo for each branch).

Demo Github address: github.com/androidjp/g…

gomock + mockgen

demand

  • Interface piling. Mock an assignable dependent member object (e.gServiceARely onRepositoryA, so, in the testServiceA.MethodAMethod is ready to mockRepositoryA)

Installation and Configuration

  1. Pull install Gomock and Mockgen

    go get -v -u github.com/golang/mock/gomock
    Copy the code

    Get $GOPATH/src/github.com/golang/mock directory with GoMock bag and two subdirectories mockgen tools. Steps 2, 3, and 4 check to see if your $GOPATH/bin directory already has a mockgen executable installed, or ignore the following steps.

  2. Go to the Mockgen subdirectory and execute the build command, which generates the executable mockgen.

  3. Copy mockgen to $GOPATH/bin;

  4. Specify that the environment variable Path contains the $GOPATH/bin directory;

  5. Finally, try hitting the command line:

    mockgen help
    Copy the code

    If -bash: mockgen: Command not found, $GOPATH/bin is not configured in your environment variable PATH.

The document

go doc github.com/golang/mock/gomock
Copy the code

Online Reference Documents

Mockgen use

  1. Open the command line in the project root directory

  2. Find a. Go file in the directory where the interface you want to mock is located and generate the corresponding mock file

    mockgen -source=1_gomock/db/repository.go  > test/1_gomock/db/mock_repository.go
    Copy the code

    Of course, if your test/1_gomock/db/ directory already exists.

  3. Then, use the MockXxx(t) method in the mock file

The key usage

1. Interface piling procedure

  1. First, use the Mockgen tool to generate mock files for the corresponding interface

  2. Then the piling began

    // 1. Initialize the mock controller
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()
    
    // 2. Initialize the mock object and inject the controller
    mockRepo := mock_gomock_db.NewMockRepository(ctrl)
    
    // 3. Set the return value of the mock object
    mockRepo.EXPECT().Create("name"And []byte("jasper")).Return(nil)
    Copy the code
  3. Then, test your logic

    // when
    demoSvr := &gomock_service.DemoService{Repo: mockRepo}
    data, err := demoSvr.InsertData("name"."jasper")
    // then
    assert.Equal(t, "success", data)
    assert.Nil(t, err)
    Copy the code

2. Return value for the first N times of interface piling definition

// The first two returns failed
mockRepo.EXPECT().Create("name"And []byte("jasper")).Return(errors.New("db connection error")).Times(2)
// The third time is normal
mockRepo.EXPECT().Create("name"And []byte("jasper")).Return(nil)
Copy the code

3. Assert the interface call order

A. After B. After C. After D. After

// Retrieve
retrieveName := mockRepo.EXPECT().Retrieve("name").Return([]byte("jasper"), nil)
// Retrieve
mockRepo.EXPECT().Update("name"And []byte("mike")).Return(nil).After(retrieveName) 
Copy the code

Method 2: InOrder

gomock.InOrder(
    // Retrieve
    mockRepo.EXPECT().Retrieve("name").Return([]byte("jasper"), nil),
    // Retrieve
    mockRepo.EXPECT().Update("name"And []byte("mike")).Return(nil),Copy the code

The Demo sample

Github.com/androidjp/g…

Gostub piling

demand

  • Global variable piling

  • Function of pile driving

  • Process of pile driving

  • Third party warehouse piling

Installation and Configuration

go get -v -u github.com/prashantv/gostub
Copy the code

The key usage

1. Global variable piling

For global variables:

var (
  GlobalCount int
  Host        string
)
Copy the code

Piling can be done like this:

// the global variable GlobalCount int piles
// global variable Host string
stubs := gostub.Stub(&GlobalCount, 10).
  Stub(&Host, "https://www.bing.cn")
defer stubs.Reset() 
Copy the code

2. Function piling

Suppose we have a function:

func Exec(cmd string, args ...string) (string, error) {
  return "".nil
}
Copy the code

So, first I’m going to write it like this:

var Exec = func(cmd string, args ...string) (string, error) {
  return "".nil
}
Copy the code

This does not affect the use of business logic.

And then piling:

Method 1: StubFunc returns results directly

stubs := gostub.StubFunc(&gomock_service.Exec, "xxx-vethName100-yyy".nil)
defer stubs.Reset()
Copy the code

Method 2: Stub can also set specific logic

stubs := gostub.Stub(&Exec, func(cmd string, args ...string) (string, error) {
      return "xxx-vethName100-yyy".nil
    })
defer stubs.Reset()
Copy the code

3. Process piling

For functions that return no value, we call them procedures:

var DestroyResource = func(a) {
  fmt.Println("Cleaning up resources, etc.")}Copy the code

Start piling:

Method 1: StubFunc returns results directly (if you want the process to do nothing, you can do this)

stubs := gostub.StubFunc(&gomock_service.DestroyResource)
defer stubs.Reset()
Copy the code

Method 2: Stub can also set specific logic

stubs := gostub.Stub(&gomock_service.DestroyResource, func(a) {
      // do sth
    })
defer stubs.Reset()
Copy the code

4. The third party warehouse drives piles

Many third-party library functions (functions, not member methods of an object) that we use a lot, are not our focus during unit testing, or want to report errors, etc.

  1. If, say, I want to pile json serialization and deserialization functions, then define the json.go file under the Adapter package and declare the object:

    package adapter
    
    import (
      "encoding/json"
    )
    
    var Marshal = json.Marshal
    var UnMarshal = json.Unmarshal 
    Copy the code
  2. In unit tests, you can use gostub.stubfunc directly to do the piling:

    // given
    var mikeStr = `{"name":"Jasper", "age":18}`
    stubs := gostub.StubFunc(&adapter.Marshal, []byte(mikeStr), nil)
    defer stubs.Reset()
    
    stu := &entity.Student{Name: "Mike", Age: 18}
    
    // when
    res, err := stu.Print()
    
    // then
    assert.Equal(t, "{\"name\":\"Jasper\", \"age\":18}", res)
    assert.Nil(t, err)
    Copy the code

The Demo sample

Github.com/androidjp/g…

Goconvey more optimized assertions

demand

  • Write test cases more elegantly

  • Better visual interface with real-time updates on all current testing conditions and coverage

Installation and Configuration

go get -v -u github.com/smartystreets/goconvey
Copy the code

How to run tests

CD Go to the test file directory and run the following command:

go test -v 
Copy the code

Or, CD to the project root directory and run tests for the entire project:

go test ./... -v -cover
Copy the code

Alternatively, run the following command to display the web page (default: http://127.0.0.1:8080) :

goconvey
Copy the code

Where goconvey -help can be used to convey the command line option, which is usually written as follows:

goconvey -port 8112 -excludedDirs "vendor,mock,proto,conf"
Copy the code

Indicates that the Web page is at http://127.0.0.1:8112 and ignores the vendor, Mock, and proto directories in the current context directory.

Here is:

Format specification

func TestXxxService_XxxMethod(t *testing.T) {
  Convey("Should return case A", t, func(a) {
    Convey("When a logical return 'XXXX'".func(a) {
            // given. Various preparation logic (mock, stub, declare, initialize, build data, etc.)// when
            res, err := xxxService.XxxMethod(....)
            // then
            So(res, ShouldEqual, "A")
        })
        Convey("When passes keyA= 'valA', keyB= 'valB'".func(a) {

        })
    })

    Convey("Should return case B", t, func(a) {
    Convey("when ..................".func(a){})})}Copy the code

Such as:

. Convey("should return 'fails to parse the response body: response is empty`", t, func() { Convey("when proxy response with statusCode 200 and empty body is returned", func() { ........Copy the code

The IDE generates unit test code quickly

The following templates are used in my practice:

Convey("should $FIRST$", t, func(a) {
    Convey("when $SECOND$".func(a) {
        //---------------------------------------
        // given
        //---------------------------------------
        $END$
        //---------------------------------------
        // when
        //---------------------------------------
        
        //---------------------------------------
        // then
        //---------------------------------------})})Copy the code

Once set up, when writing tests, type SWG and hit TAB to generate this template code.

Note: the test code, of course, need to introduce convey library: import. “github.com/smartystreets/goconvey/convey”

The Demo sample

androidjp/go-mock-best-practice

GoMonkey

demand

  • Piling for a function

  • Piling for a process

  • Piling for a method

  • Special scenario: a case of pile within pile

Usage scenarios

  • Basic scenario: Piling a function

  • Basic scenario: Piling for a process

  • Basic scenario: Piling for a method

  • Special scenario: a case of pile within pile

limited

  • Monkey is not thread-safe; do not use the Monkey for concurrent testing

  • Invalid piling for inline functions (generally required: disable inline with the command line argument -gcflags=-l)

    // Functions like this are very simple and short. They have a function structure at the source level, but do not have the properties of functions when compiled.
    func IsEqual(a, b string) bool {
        return a == b
    }
    Copy the code
  • The Monkey can only pile up methods/functions that begin with a capital letter (which, of course, is more consistent with the code specification).

  • The API is not elegant enough and does not support complex situations where multiple calls to stub functions (methods) show different behaviors.

The installation

go get -v bou.ke/monkey
Copy the code

Running unit tests

Method 1: Run the cli

go test -gcflags=-l -v
Copy the code

IDE run: Add the option -gcFlags =-l to the Go Tool Arguments column

Note: An error may occur when running tests.

The reason: Because it replaces a pointer to a function at run time, if it encounters simple functions such as rand.int63n and time.now, the compiler may inline the function directly to the code where the call actually took place rather than call the original method. So using this approach often requires us to specify -gcflags=-l to disable the compiler’s inline optimization during testing.

At this point, there are several ways to run:

  1. Command line operation
    go test -gcflags=-l -v
    Copy the code
  2. IDE runs, plus-gcflags=-l

The key usage

1. Function piling

  1. Suppose we have a function Exec:

    package service
    
    func Exec(cmd string, args ...string) (string, error) {
        / /...
    }
    Copy the code
  2. We can just pile it using Monkey. Patch without declaring any variables:

    / / piling
    guard := Patch(service.Exec, func(cmd string, args ...string) (string, error) {
        return "sss".nil
    })
    defer guard.Unpatch()
    / / call
    output, err := service.Exec("cmd01"."--conf"."conf/app_test.yaml")
    Copy the code

2. Process piling

As with functions, the advantage over GoStub is that there is no need to declare variables to point to this function, thus reducing the need for business code modification.

  1. Suppose there is a process like this:
    func InternalDoSth(mData map[string]interface{}) {
        mData["keyA"] = "valA"
    }
    Copy the code
  2. Piling in the same way
    patchGuard := Patch(service.InternalDoSth, func(mData map[string]interface{}) {
        mData["keyA"] = "valB"
    })
    defer patchGuard.Unpatch()
    
    ..............
    Copy the code

3. Method piling

Note: Only Public methods can be staked, i.e., capitalized methods

  1. Suppose we have a class and its method definition:

    type Etcd struct{}// Member methods
    func (e *Etcd) Get(id int) []string {
        names := make([]string.0)
        switch id {
        case 0:
            names = append(names, "A")
        case 1:
            names = append(names, "B")}return names
    }
    
    func (e *Etcd) Save(vals []string) (string, error) {
        return "DB saved successfully".nil
    }
    
    func (e *Etcd) GetAndSave(id int) (string, error) {
        vals := e.Get(id)
        if vals[0] = ="A" {
            vals[0] = "C"
        }
        return e.Save(vals)
    }
    Copy the code
  2. Use PatchInstanceMethod to pile and call:

    / / piling
    var e = &service.Etcd{}
    guard := PatchInstanceMethod(reflect.TypeOf(e), "Get".func(e *service.Etcd, id int) []string {
        return []string{"Jasper"}})defer guard.Unpatch()
    
    / / call
    res := e.Get(1)
    Copy the code
  3. When I want to stake multiple member methods in a test case, I can do this:

    var e = &service.Etcd{}
    // stub Get
    theVals := make([]string.0)
    theVals = append(theVals, "A")
    PatchInstanceMethod(reflect.TypeOf(e), "Get".func(e *service.Etcd, id int) []string {
        return theVals
    })
    // stub Save
    PatchInstanceMethod(reflect.TypeOf(e), "Save".func(e *service.Etcd, vals []string) (string, error) {
        return "", errors.New("occurs error")})// Delete all patches in one click
    defer UnpatchAll()
    
    .............
    Copy the code

4. Fit gomock (pile-in-pile)

Used when I need to mock out an interface and redefine a method of the mock object.

See the demo example repo_test.go for details

The Demo sample

androidjp/go-mock-best-practice

pit

The GoConvey command cannot run test cases properly after GoMonkey Patch is used

Solution:

  • Using the commandgo test ./... -v -cover -gcflags "all=-N -l"Run tests;
  • Manually modify GoCONVEY, refer to the following article for details:

Blog.csdn.net/scun_cg/art…

Native HTTP service interface testing

demand

  • nativenet/httpWrite web services, HTTP interfaces need unit testing.

example

Let’s say we don’t use any Web frameworks (Gin, Beego, Echo, etc.) and write a RESTful interface service using the native Golang NET/HTTP library like this:

  1. Define the controller

    var (
      instanceDemoController *DemoController
      initDemoControllerOnce sync.Once
    )
    
    type DemoController struct{}func GetDemoController(a) *DemoController {
      initDemoControllerOnce.Do(func(a) {
        instanceDemoController = &DemoController{}
      })
      return instanceDemoController
    }
    
    func (d *DemoController) GetMessage(w http.ResponseWriter, r *http.Request) {
      r.ParseForm()       // Parses parameters, which are not parsed by default
      fmt.Println(r.Form) // This information is printed to the server
      fmt.Println("path", r.URL.Path)
      fmt.Println("scheme", r.URL.Scheme)
      fmt.Println(r.Form["url_long"])
      for k, v := range r.Form {
        fmt.Println("key:", k)
        fmt.Println("val:", strings.Join(v, ""))
      }
      fmt.Fprintf(w, "Hello Mike!") // What is written to w is output to the client
    }
    
    Copy the code
  2. Use the HandleFunc and ListenAndServe methods of the HTTP package directly in main mode to complete the listening and start the service:

    func main(a) {
      // Set the access route
      http.HandleFunc("/message", controller.GetDemoController().GetMessage)
      // Set the listening port
      fmt.Println(9090 "Start listening, try to request: http://localhost:9090/message? keyA=valA&url_long=123456")
      if err := http.ListenAndServe(": 9090".nil); err ! =nil {
        log.Fatal("ListenAdnServe: ", err)
      }
    }
    Copy the code

    So, what if I want to unit test this interface? Native net/ HTTP /httptest can help you:

    //---------------------------------------
    // given
    //---------------------------------------
    demoCtrl := &controller.DemoController{}
    ts := httptest.NewServer(http.HandlerFunc(demoCtrl.GetMessage))
    defer ts.Close()
    
    //---------------------------------------
    // when
    //---------------------------------------
    resp, err := http.Get(ts.URL)
    defer resp.Body.Close()
    bodyBytes, err := ioutil.ReadAll(resp.Body)
    
    //---------------------------------------
    // then
    //---------------------------------------
    assert.Nil(t, err)
    assert.Equal(t, "Hello Mike!".string(bodyBytes))
    Copy the code

The Demo sample

androidjp/go-mock-best-practice

Gin interface test

Gin official documentation: github.com/gin-gonic/g…

demand

  • Test API interfaces written based on Gin framework.

The installation

Interfaces can be tested using native HttpTest, or other libraries such as Apitest

// gin
go get -v -u github.com/gin-gonic/gin
// Potency assertion library
go get -v -u github.com/stretchr/testify

Copy the code

Native test writing

  1. First of all, the logic of our DemoController code is basically the same, we just need to use *gin.Context, and the startup function is just more concise:

    func main(a) {
      // Set the access route
      r := gin.Default()
      r.GET("/message", controller.GetDemoController().GetMessage)
    
      // Set the listening port
      fmt.Println(9090 "Start listening, try to request: http://localhost:9090/message? keyA=valA&url_long=123456")
      if err := r.Run(": 9090"); err ! =nil {
        log.Fatal("ListenAdnServe: ", err)
      }
    }
    Copy the code
  2. Therefore, the test case writing method is just different from the logic of the gin test environment in the preliminary preparation:

    //---------------------------------------
    // given
    //---------------------------------------
    gin.SetMode(gin.TestMode)
    router := gin.New()
    demoCtrl := &controller.DemoController{}
    // The interface to test
    router.GET("/message", demoCtrl.GetMessage)
    
    //---------------------------------------
    // when
    //---------------------------------------
    // Build the return value
    w := httptest.NewRecorder()
    // Build the request
    req, _ := http.NewRequest("GET"."/message? keyA=valA&url_long=123456".nil)
    // Invoke the request interface
    router.ServeHTTP(w, req)
    
    resp := w.Result()
    body, err := ioutil.ReadAll(resp.Body)
    
    //---------------------------------------
    // then
    //---------------------------------------
    assert.Nil(t, err)
    assert.Equal(t, "Hello Mike!".string(body))
    Copy the code

The Demo sample

androidjp/go-mock-best-practice

apitest

demand

  • Make it easier to test RESTFul apis in frameworks such as Golang native HTTP or Gin

The installation

Liverpoolfc.tv: apitest. Dev /

Github address: github.com/steinfletch…

go get -u github.com/steinfletcher/apitest

Test the Gin interface with Apitest

See the official documentation for more details on the various apitest uses, but here’s just one of the biggest differences between using native HttpTest earlier: it’s much simpler.

//---------------------------------------
// given
//---------------------------------------
gin.SetMode(gin.TestMode)
router := gin.New()
demoCtrl := &controller.DemoController{}
// The interface to test
router.GET("/message", demoCtrl.GetMessage)

//---------------------------------------
// when then
//---------------------------------------
apitest.New().
  Handler(router).
  Getf("/message? keyA=%s&url_long=1%s"."valA"."123456").
  Header("Client-Type"."pc").
  Cookie("sid"."id001").
  JSON(nil).
  Expect(t).
  Status(http.StatusOK).
  Assert(jsonPath.Equal(`$.code`.float64(2000))).
  Assert(jsonPath.Equal(`$.msg`."Hello Mike!")).
  Body(`{"code":2000,"msg":"Hello Mike!" } `).
  End()
Copy the code

The Demo sample

androidjp/go-mock-best-practice

Beego interface testing

Reference article: blog.csdn.net/qq_38959696…

SqlMock use

Github:github.com/DATA-DOG/go…

demand

  • Test your OWN SQL scripts and DB access related to the details of the logic there is no problem

The installation

go get -v -u github.com/DATA-DOG/go-sqlmock
Copy the code

The key usage

Case 1: Take the DB object directly as an input parameter

Suppose we have a function that directly passes in *sql.DB:

func (d *DemoService) AddStudentDirectly(db *sql.DB, name string) (stu *entities.Student, err error) {
  // Start the transaction
  tx, err := db.Begin()
  iferr ! =nil {
    return nil, err
  }

  defer func(a) {
    switch err {
    case nil:
      err = tx.Commit()
    default:
      tx.Rollback()
    }
  }()

  // 1. Add a student information
  result, err := db.Exec("insert into students(name) values(?) ", name)
  iferr ! =nil {
    return
  }
  id, err := result.LastInsertId()
  iferr ! =nil {
    return
  }
  // 2. Then, add the student to classroom 1
  if _, err = db.Exec("insert into classroom_1(stu_id) values(?) ", id); err ! =nil {
    return
  }
  stu = &entities.Student{ID: id, Name: name}
  return
}
Copy the code

The above functions do: open the transaction, insert the STUDENTS table, the id to be obtained, insert another long classRoom_1 table, and finally commit the transaction.

In this case, just mock out the db * SQl. db object:

  1. Sqlmock.new () returns the db object that you mock out, as well as the mock logger object;

    db, mock, err := sqlmock.New()
    iferr ! =nil {
      t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
    }
    defer db.Close()
    Copy the code
  2. Set what DB operation is going to happen, and set what value will be returned:

    // 1) The transaction will start first
    mock.ExpectBegin()
    // 2) select * from students where id=1
    mock.ExpectExec(regexp.QuoteMeta(`insert into students(name) values(?) `)).WithArgs("mike").WillReturnResult(sqlmock.NewResult(1.1))
    // 3) Insert table classroom_1
    mock.ExpectExec(regexp.QuoteMeta("insert into classroom_1(stu_id) values(?) ")).WithArgs(1).WillReturnResult(sqlmock.NewResult(1.1))
    // 4) Commit transaction
    mock.ExpectCommit()
    Copy the code
  3. Mock db object as an input parameter:

    . stu, err := svr.AddStudentDirectly(db,"mike")...Copy the code

Case 2: It has its own Repository layer object that encapsulates db operations

A XxxRepository class in the repository layer has a DB connection object, and then a set of db-related operations:

type MySQLRepository struct {
  db *sql.DB
}

func NewMySQLRepository(a) *MySQLRepository {
  db, err := sql.Open("mysql"."Root: a root @ TCP (192.168.200.128:3307)/test? charset=utf8mb4")
  iferr ! =nil {
    panic(err)
  }
  db.SetConnMaxLifetime(time.Minute * 2)
  db.SetMaxOpenConns(10)
  db.SetMaxIdleConns(10)

  return &MySQLRepository{
    db: db,
  }
}

func (m *MySQLRepository) CreateStudent(name string) (stu *entities.Student, err error){... }Copy the code

At this point, if you want to mock it better, you need to configure the GoStub framework:

  1. First of all, the source code needs to be slightly tweaked, with adapter adapting SQL.Open to adapter.open:

    package adapter
    
    import "database/sql"
    
    var Open = sql.Open
    Copy the code
  2. Then, also using SQLMock to define the db object of the mock:

    db, mock, err := sqlmock.New()
    iferr ! =nil {
      t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
    }
    defer db.Close()
    
    mock.ExpectBegin()
    mock.ExpectExec(regexp.QuoteMeta(`insert into students(name) values(?) `)).WithArgs("mike").WillReturnResult(sqlmock.NewResult(1.1))
    mock.ExpectExec(regexp.QuoteMeta("insert into classroom_1(stu_id) values(?) ")).WithArgs(1).WillReturnResult(sqlmock.NewResult(1.1))
    mock.ExpectCommit()
    Copy the code
  3. Gostub then pins adapter.Open to return our mock DB object:

    stubs := gostub.StubFunc(&adapter.Open, db, nil)
    defer stubs.Reset()
    Copy the code
  4. Finally, the actual running logic was tested:

    sqlRepository := repository.NewMySQLRepository()
    student, err := sqlRepository.CreateStudent("mike") 
    Copy the code

    Of course, there are some different encapsulation logic for different ORM frameworks, and the obtained DB operation object may not be of * SQL.db type. See the Demo examples for more details. Here we have only practiced the mock practice of MySQL GORM and XORM frameworks.

The Demo sample

  • MySQL native SQL-driver mock: github.com/androidjp/g…

  • MySQL GorM ORM Framework Mock: github.com/androidjp/g…

  • MySQL Xorm ORM Framework Mock: github.com/androidjp/g

other

How to mock Gin * Gin.Context

  1. First, define a MockResponseWriter structure in the test file:

    type MockResponseWriter struct{}func (m *MockResponseWriter) Header(a) http.Header {
      h := make(map[string] []string)
      h["Client-Type"] = []string{"wps-pc"}
      return h
    }
    
    func (m *MockResponseWriter) Write([]byte) (int, error) {
      return 0.nil
    }
    
    func (m *MockResponseWriter) WriteHeader(statusCode int){}Copy the code
  2. Construct gin.Context using gin.CreateTestContext

    mockGinContext, _ := gin.CreateTestContext(&MockResponseWriter{})
    mockGinContext.Request = &http.Request{}
    // mock request header
    mockGinContext.Request.Header = make(map[string] []string)
    mockGinContext.Request.Header["Client-Type"] = []string{"pc"}
    mockGinContext.Request.Header["Client-Chan"] = []string{"1.2.0"}
    mockGinContext.Request.Header["Client-Ver"] = []string{"1.0.1"}
    mockGinContext.Request.Header["X-Forwarded-Host"] = []string{"test.com"}
    mockGinContext.Request.URL = &url.URL{Path: "/api/v2/test"}
    mockGinContext.Set("id"."123123123")
    
    // mock request body
    mockGinContext.Request.Body = ioutil.NopCloser(bytes.NewReader([]byte("{\"key\":\"val\",\"userid\":123123}")))
    Copy the code
  3. Ok, this mockGinContext is ready to be used as an argument.

How to simulate POST request to upload file using multipart/form-data form

POST data using the content-type multipart/form-data

In short, you’ll need to use the mime/multipart package to build the form.

The sample code:

 package main

import (
    "bytes"
    "fmt"
    "io"
    "mime/multipart"
    "net/http"
    "net/http/httptest"
    "net/http/httputil"
    "os"
    "strings"
)

func main(a) {

    var client *http.Client
    var remoteURL string
    {
        //setup a mocked http client.
        ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            b, err := httputil.DumpRequest(r, true)
            iferr ! =nil {
                panic(err)
            }
            fmt.Printf("%s", b)
        }))
        defer ts.Close()
        client = ts.Client()
        remoteURL = ts.URL
    }

    //prepare the reader instances to encode
    values := map[string]io.Reader{
        "file":  mustOpen("main.go"), // lets assume its this file
        "other": strings.NewReader("hello world!"),
    }
    err := Upload(client, remoteURL, values)
    iferr ! =nil {
        panic(err)
    }
}

func Upload(client *http.Client, url string, values map[string]io.Reader) (err error) {
    // Prepare a form that you will submit to that URL.
    var b bytes.Buffer
    w := multipart.NewWriter(&b)
    for key, r := range values {
        var fw io.Writer
        if x, ok := r.(io.Closer); ok {
            defer x.Close()
        }
        // Add an image file
        if x, ok := r.(*os.File); ok {
            iffw, err = w.CreateFormFile(key, x.Name()); err ! =nil {
                return}}else {
            // Add other fields
            iffw, err = w.CreateFormField(key); err ! =nil {
                return}}if_, err = io.Copy(fw, r); err ! =nil {
            return err
        }

    }
    // Don't forget to close the multipart writer.
    // If you don't close it, your request will be missing the terminating boundary.
    w.Close()

    // Now that you have a form, you can submit it to your handler.
    req, err := http.NewRequest("POST", url, &b)
    iferr ! =nil {
        return
    }
    // Don't forget to set the content type, this will contain the boundary.
    req.Header.Set("Content-Type", w.FormDataContentType())

    // Submit the request
    res, err := client.Do(req)
    iferr ! =nil {
        return
    }

    // Check the response
    ifres.StatusCode ! = http.StatusOK { err = fmt.Errorf("bad status: %s", res.Status)
    }
    return
}

func mustOpen(f string) *os.File {
    r, err := os.Open(f)
    iferr ! =nil {
        panic(err)
    }
    return r
}
Copy the code

Thanks for watching