将HTTP请求发送到API时如何模拟

I have implemented a ReST API in Go using go-gin and I am trying to test a handler function which looks like the following

func editNameHandler(c *gin.Context) {
        // make a ReST call to another server
        callToAnotherServer()
        c.Status(200)
}

I want to to mock callToAnotherServer method so that my test case doesn't call the 3rd party server at all.

My test case looks like

 func TestSeriveIdStatusRestorePatch(t *testing.T) {

    // Request body
    send := strings.NewReader(`{"name":"Robert"}` 

    // this function sends an HTTP request to the API which ultimately calls editNameHandler
    // Ignore the variables.The variables are retrieved in code this is to simplify question
    ValidTokenTestPatch(API_VERSION+"/accounts/"+TestAccountUUID+"/students/"+TestStudentId, t, send, http.StatusOK)        
}

I went through Mock functions in Go which mentions how we can pass a function to mock. I am wondering how we can pass a function while sending http request? How can I mock function in such case. What is the best practice?

I don't think there is single response for this question, but I'll share my approach on how I'm currently doing Dependency Injection on Go with go-gin (but should be the nearly the same with any other router).

From a business point of view, I have a struct that wraps all access to my services which are responsible for business rules/processing.

// WchyContext is an application-wide context
type WchyContext struct {
    Health   services.HealthCheckService
    Tenant   services.TenantService
    ... whatever
}

My services are then just interfaces.

// HealthCheckService is a simple general purpose health check service
type HealthCheckService interface {
    IsDatabaseOnline() bool
}

Which have mulitple implementations, like MockedHealthCheck, PostgresHealthCheck, PostgresTenantService and so on.

My router than depends on a WchyContext, which the code looks like this:

func GetMainEngine(ctx context.WchyContext) *gin.Engine {
    router := gin.New()
    router.Use(gin.Logger())
    router.GET("/status", Status(ctx))
    router.GET("/tenants/:domain", TenantByDomain(ctx))
    return router
}`

Status and TenantByDomain act like a handler-factory, all it does is create a new handler based on given context, like this:

type statusHandler struct {
    ctx context.WchyContext
}

// Status creates a new Status HTTP handler
func Status(ctx context.WchyContext) gin.HandlerFunc {
    return statusHandler{ctx: ctx}.get()
}

func (h statusHandler) get() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.JSON(200, gin.H{
            "healthy": gin.H{
                "database": h.ctx.Health.IsDatabaseOnline(),
            },
            "now":     time.Now().Format("2006.01.02.150405"),
        })
    }
}

As you can see, my health check handler doesn't care about concrete implementation of my services, I just use it whatever is in the ctx.

The last part depends on current execution environment. During automated tests I create a new WchyContext using mocked/stubbed services and send it to GetMainEngine, like this:

ctx := context.WchyContext{
    Health: &services.InMemoryHealthCheckService{Status: false},
    Tenant: &services.InMemoryTenantService{Tenants: []*models.Tenant{
        &models.Tenant{ID: 1, Name: "Orange Inc.", Domain: "orange"},
        &models.Tenant{ID: 2, Name: "The Triathlon Shop", Domain: "trishop"},
    }}
}
router := handlers.GetMainEngine(ctx)

request, _ := http.NewRequest(method, url, nil)
response := httptest.NewRecorder()
router.ServeHTTP(response, request)
... check if response matches what you expect from your handler

And when you setup it to really listen to a HTTP port, the wiring up looks like this:

var ctx context.WchyContext
var db *sql.DB

func init() {
    db, _ = sql.Open("postgres", os.Getenv("DATABASE_URL"))

    ctx = context.WchyContext{
        Health: &services.PostgresHealthCheckService{DB: db},
        Tenant: &services.PostgresTenantService{DB: db}
    }
}

func main() {
    handlers.GetMainEngine(ctx).Run(":" + util.GetEnvOrDefault("PORT", "3000"))
}

There are a few things that I don't like about this, I'll probably refactor/improve it later, but it has been working well so far.

If you want to see full code reference, I'm working on this project here https://github.com/WeCanHearYou/wchy

Hope it can help you somehow.