如何使用pgx驱动程序为调用与Golang中的db交互的函数的处理程序编写单元测试?

h6my8fg2  于 2023-03-27  发布在  Go
关注(0)|答案(2)|浏览(103)

我一直在尝试为我的http处理程序编写单元测试。代码段如下:

func (s *Server) handleCreateTicketOption(w http.ResponseWriter, r *http.Request) {
    var t ticket.Ticket
    body, err := ioutil.ReadAll(r.Body)
    if err != nil {
        http.Error(w, er.ErrInternal.Error(), http.StatusInternalServerError)
        return
    }
    err = json.Unmarshal(body, &t)
    if err != nil {
        http.Error(w, er.ErrInvalidData.Error(), http.StatusBadRequest)
        return
    }

    ticket, err := s.TicketService.CreateTicketOption(r.Context(), t)
    if err != nil {
        http.Error(w, er.ErrInternal.Error(), http.StatusInternalServerError)
        return
    }

    res, err := json.Marshal(ticket)
    if err != nil {
        http.Error(w, er.ErrInternal.Error(), http.StatusInternalServerError)
        return
    }

    log.Printf("%v tickets allocated with name %v\n", t.Allocation, t.Name)
    s.sendResponse(w, res, http.StatusOK)
}

与数据库交互的实际逻辑。此代码段由处理程序调用,如您在上面的代码中所见。ticket, err := s.TicketService.CreateTicketOption(r.Context(), t)

func (t *TicketService) CreateTicketOption(ctx context.Context, ticket ticket.Ticket) (*ticket.Ticket, error) {
    tx, err := t.db.dbPool.Begin(ctx)
    if err != nil {
        return nil, er.ErrInternal
    }
    defer tx.Rollback(ctx)

    var id int
    err = tx.QueryRow(ctx, `INSERT INTO ticket (name, description, allocation) VALUES ($1, $2, $3) RETURNING id`, ticket.Name, ticket.Description, ticket.Allocation).Scan(&id)
    if err != nil {
        return nil, er.ErrInternal
    }

    ticket.Id = id

    return &ticket, tx.Commit(ctx)
}

这就是我对处理程序的单元测试。

func TestCreateTicketOptionHandler(t *testing.T) {
    

    caseExpected, _ := json.Marshal(&ticket.Ticket{Id: 1, Name: "baris", Description: "test-desc", Allocation: 10})
    srv := NewServer()
    // expected := [][]byte{
    //  _, _ = json.Marshal(&ticket.Ticket{Id: 1, Name: "baris", Description: "test-desc", Allocation: 20}),
    //  // json.Marshal(&ticket.Ticket{Id: 1, Name: "baris", Description: "test-desc", Allocation: 20})
    // }

    tt := []struct {
        name  string
        entry *ticket.Ticket
        want  []byte
        code  int
    }{
        {
            "valid",
            &ticket.Ticket{Name: "baris", Description: "test-desc", Allocation: 10},
            caseExpected,
            http.StatusOK,
        },
    }

    var buf bytes.Buffer
    for _, tc := range tt {
        t.Run(tc.name, func(t *testing.T) {
            json.NewEncoder(&buf).Encode(tc.entry)
            req, err := http.NewRequest(http.MethodPost, "/ticket_options", &buf)
            log.Println("1")
            if err != nil {
                log.Println("2")
                t.Fatalf("could not create request: %v", err)
            }
            log.Println("3")
            rec := httptest.NewRecorder()

            log.Println("4")
            srv.handleCreateTicketOption(rec, req)
            log.Println("5")
            if rec.Code != tc.code {
                t.Fatalf("got status %d, want %v", rec.Code, tc.code)
            }
            log.Println("6")
            if reflect.DeepEqual(rec.Body.Bytes(), tc.want) {
                log.Println("7")
                t.Fatalf("NAME:%v,  got %v, want %v", tc.name, rec.Body.Bytes(), tc.want)
            }
        })
    }
}

我做了一些关于模拟pgx的研究,大多数都是测试逻辑部分而不是通过处理程序。我想为处理程序和逻辑本身分别编写单元测试。然而,我为处理程序编写的单元测试如下所示

github.com/bariis/gowit-case-study/psql.(*TicketService).CreateTicketOption(0xc000061348, {0x1485058, 0xc0000260c0}, {0x0, {0xc000026dd0, 0x5}, {0xc000026dd5, 0x9}, 0xa})
        /Users/barisertas/workspace/gowit-case-study/psql/ticket.go:24 +0x125
github.com/bariis/gowit-case-study/http.(*Server).handleCreateTicketOption(0xc000061340, {0x1484bf0, 0xc000153280}, 0xc00018e000)
        /Users/barisertas/workspace/gowit-case-study/http/ticket.go:77 +0x10b
github.com/bariis/gowit-case-study/http.TestCreateTicketOptionHandler.func2(0xc000119860)
        /Users/barisertas/workspace/gowit-case-study/http/ticket_test.go:80 +0x305

psql/ticket.go:24tx, err := t.db.dbPool.Begin(ctx)
http/ticket.go:77ticket, err := s.TicketService.CreateTicketOption(r.Context(), t)
http/ticket_test.go:80srv.handleCreateTicketOption(rec, req)
我如何模仿这种类型的代码?

ccgok5k5

ccgok5k51#

1.创建具有所需DB函数的接口
1.你的数据库处理程序实现了这个接口。你在实际执行中使用这个处理程序
1.使用testi/mock创建一个模拟处理程序,并在测试用例中使用它代替DB处理程序

qlzsbp2j

qlzsbp2j2#

从我所读到的,你有以下结构:

type Server struct {
   TicketService ticket.Service
}

type TicketService struct {
    db *sql.Db // ..or similar
}

func (ts *TicketService) CreateTicketOption(...)

模拟这一点的技巧是确保TicketService是一个接口而不是结构体
就像这样:

type TicketService interface {
  CreateTicketOption(ctx context.Context, ticket ticket.Ticket) (*ticket.Ticket, error) {
}

通过这样做,您的Server需要一个TicketService接口。然后您可以这样做:

type postgresTicketService struct {
    db *sql.Db 
}

func (pst *postgresTicketService) CreateTicketOption(...)...

这意味着postgresTicketService满足作为ticket.Service传递到Server的要求。
这也意味着你可以这样做:

type mockTicketService struct {
}

func (mts *mockTicketService) CreateTicketOption(...)...

这样就可以将Server从实际实现中分离出来,并且可以在测试时使用mockTicketService初始化服务器,在部署时使用postgresTicketService初始化服务器。

相关问题