Макет функции того же пакета - PullRequest
0 голосов
/ 23 октября 2019

Я сейчас вхожу в юнит-тесты на Go. Оказывается, мне нужно пересмотреть свой способ написания кода на go, поскольку (я чувствую и понимаю), мне нужно, чтобы все мои функции были частью данного интерфейса {} / структурированы, а не были объявлены в пакете.

Допустим, у меня есть 3 слоя для моего сервера микро-услуг gRPC

  • main
  • обработчики
  • dal

handlers потребуется что-то вроде этого:

type IUsersServiceHandlers interface {
    AddUserHandler(ctx context.Context, in *pb.AddUserRequest) (*pb.AddUserResponse, error)
    GetUserByIDHandler(ctx context.Context, in *pb.GetUserByIDRequest) (*pb.GetUserByIDResponse, error)
    GetUserByIDsHandler(ctx context.Context, in *pb.GetUserByIDsRequest) (*pb.GetUserByIDsResponse, error)
    GetAllUsersHandler(ctx context.Context, in *pb.GetAllUsersRequest) (*pb.GetAllUsersResponse, error)
}

type UsersServiceHandlers struct{
    DAL dal.IUsersServiceDAL
}

// AddUserHandler adds an user into the database.
func (h *UsersServiceHandlers) AddUserHandler(ctx context.Context, in *pb.AddUserRequest) (*pb.AddUserResponse, error) { /**/ }

// GetUserByIDHandler returns an user from the database.
func (h *UsersServiceHandlers) GetUserByIDHandler(ctx context.Context, in *pb.GetUserByIDRequest) (*pb.GetUserByIDResponse, error) { /**/ }

// GetUserByIDsHandler returns a slice of users from the database.
func (h *UsersServiceHandlers) GetUserByIDsHandler(ctx context.Context, in *pb.GetUserByIDsRequest) (*pb.GetUserByIDsResponse, error) { /**/ }

// GetAllUsersHandler returns all the users from the database.
func (h *UsersServiceHandlers) GetAllUsersHandler(ctx context.Context, in *pb.GetAllUsersRequest) (*pb.GetAllUsersResponse, error) { /**/ }

Если я хочу создать модульный тест, я просто высмеиваю dal.IUsersServiceDAL и напишу свой тест.

// UsersServiceDAL Mock
type UsersServiceDALMock struct {
    mock.Mock
}
/* Mock implementation funcs ... */

func TestAddUserHandler(t *testing.T) {
    usersServiceDALMock := new(UsersServiceDALMock)
    var user = &models.User{
        ID:         mongodb.GenerateNewObjectID(),
        UserName:   "user",
    }
    ctx, _ := context.WithTimeout(context.Background(), 2*time.Second)
    usersServiceDALMock.On("InsertOne", user).Return(user)

    // next we want to define the service we wish to test
    usersServiceHandlers := UsersServiceHandlers{usersServiceDALMock}
    // and call said method
    a := assert.New(t)
    response, err := usersServiceHandlers.AddUserHandler(ctx, &pb.AddUserRequest{
        ID:                   user.ID.Hex(),
        UserName:             user.UserName,
    })
    a.Nil(err, "AddUserHandler should've worked.")

    a.NotNil(response.User, "The response's User shouldn't be nil.")
    usersServiceDALMock.AssertExpectations(t)
}

Однако,возникает проблема (которая имеет смысл для меня). Я даю пользователю только 2 данных, идентификатор и имя пользователя. Тем не менее, это поле содержит другие поля, которые обновляются при необходимости из следующего метода:

func (h *UsersServiceHandlers) initBaseUserForCreation(user *models.User) *models.User {

    // Check if the ID is nil or not initialized. If so, we generate one.
    if user.ID == nil || user.ID.IsZero() {
        user.ID = mongodb.GenerateNewObjectID()
    }

    // We set the date to now if they aren't set.
    if user.CreatedAt.IsZero() {
        user.CreatedAt = time.Now().UTC()
    }
    if user.UpdatedAt.IsZero() {
        user.UpdatedAt = time.Now().UTC()
    }

    /*
        We don't set `IsArchived` since by default a bool is false. If it needs to be true, then the field might be already set or will be.
    */

    return user
}

Меня беспокоит, что AddUserHandler вызывает эту функцию по умолчанию, но эта функция находится в том же пакете (фактически записанавыше текущей реализации AddUserHandler). Я хотел бы иметь возможность mock этой функции (которая находится в том же пакете), чтобы она возвращала все остальные поля, не инициализированные, чтобы мой тест мог пройти.

Реализация AddUserHandler

// AddUserHandler adds an user into the database.
func (h *UsersServiceHandlers) AddUserHandler(ctx context.Context, in *pb.AddUserRequest) (*pb.AddUserResponse, error) {

    if in.ID == "" {
        return nil, status.Error(codes.InvalidArgument, stacktracer.NewStackTraceAsString(pb.InvalidArgument.ToInt32(), "Invalid argument. ID cannot be null or empty", nil))
    } else if in.UserName == "" {
        return nil, status.Error(codes.InvalidArgument, stacktracer.NewStackTraceAsString(pb.InvalidArgument.ToInt32(), "Invalid argument. UserName cannot be null or empty.", nil))
    }

    user := h.initBaseUserForCreation(&models.User{
        ID:       mongodb.ConvertStringIdToObjectID(in.ID),
        UserName: in.UserName,
    })

    insertedID, trace := h.DAL.InsertOne(user)
    if trace != nil {
        return nil, status.Error(codes.Internal, stacktracer.NewStackTraceAsString(pb.UserInsertionError.ToInt32(), "An error happened while inserting the user.", trace))
    }

    if *insertedID != *mongodb.ConvertStringIdToObjectID(in.ID) {
        return nil, status.Error(codes.Aborted, stacktracer.NewStackTraceAsString(pb.InsertedDocumentIDMismatchRequirements.ToInt32(), "Inserted document ID mismatch base ID.", nil))
    }

    return &pb.AddUserResponse{User: mappers.FromUserModelToUserPb(user, nil)}, nil
}
...