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.g
ServiceA
Rely onRepositoryA
, so, in the testServiceA.MethodA
Method is ready to mockRepositoryA
)
Installation and Configuration
-
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.
-
Go to the Mockgen subdirectory and execute the build command, which generates the executable mockgen.
-
Copy mockgen to $GOPATH/bin;
-
Specify that the environment variable Path contains the $GOPATH/bin directory;
-
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
-
Open the command line in the project root directory
-
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.
-
Then, use the MockXxx(t) method in the mock file
The key usage
1. Interface piling procedure
-
First, use the Mockgen tool to generate mock files for the corresponding interface
-
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
-
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.
-
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
-
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:
- Command line operation
go test -gcflags=-l -v Copy the code
- IDE runs, plus
-gcflags=-l
The key usage
1. Function piling
-
Suppose we have a function Exec:
package service func Exec(cmd string, args ...string) (string, error) { / /... } Copy the code
-
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.
- Suppose there is a process like this:
func InternalDoSth(mData map[string]interface{}) { mData["keyA"] = "valA" } Copy the code
- 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
-
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
-
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
-
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 command
go 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
- native
net/http
Write 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:
-
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
-
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
-
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
-
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:
-
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
-
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
-
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:
-
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
-
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
-
Gostub then pins adapter.Open to return our mock DB object:
stubs := gostub.StubFunc(&adapter.Open, db, nil) defer stubs.Reset() Copy the code
-
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
-
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
-
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
-
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