Golang, как проверить функцию, возвращающую тип канала?

Пытаюсь протестировать функцию StartP,
Ожидайте, что Start() должен быть вызван 1 раз, Done() должен быть вызван 1 раз

но у меня проблема с блокировкой теста при выполнении этого шага <-ps.Done()

Я ожидаю, что <-ps.Done() вернет ноль

Как я могу протестировать функцию, которая возвращает тип chan?

// production code

func (s *vService) StartP(ctx context.Context, reason string) error {

    ps, err := s.factory.CreateVService(ctx)
    if err != nil {
        return err
    }
    ps.Start(reason)

    err = <-ps.Done()   // code stop here to wait ? how can i test ?

    if err != nil {
        return err
    }
    return nil
}
// test code

func Test_StartP(t *testing.T) {
    mockCtrl := gomock.NewController(t)
    defer mockCtrl.Finish()

    mockPService := mockpservice.NewMockPInterface(mockCtrl)

    vService := &vService {
                      factory: &servicefactory.FakeServiceFactory{
                                 MockPService: mockPService
                               }
                    }

    mockPService.EXPECT().Start("reason").Times(1).Return()
    mockPService.EXPECT().Done().Times(1).DoAndReturn(func() chan error {
        return nil
    })

    err := vService.StartP(context.Background(), "reason")
    assert.Equal(t, nil, err)
}

Я использую gomock, чтобы издеваться над PServiceInterface.

// interface

type PServiceInterface interface {
    Start(reason string)
    Done() <-chan error
}

gomock сгенерировать эту функцию

func (m *MockProvisionServiceInterface) Done() <-chan error {
        m.ctrl.T.Helper()
        ret := m.ctrl.Call(m, "Done")
        ret0, _ := ret[0].(<-chan error)
        fmt.Println(ret0,".....mock Done()")
        return ret0
}

// Я также пробую это

    mockProvisionService.EXPECT().Done().Times(1).DoAndReturn( func() chan error {
        fmt.Println("DoAndReturn...err nil")

        ch := make(chan error, 1)
        ch <- nil
        return ch
    })
go.dev/ref/spec#Receive_operator: "Прием с нулевого канала блокируется навсегда. Операция приема на закрытом канале всегда может быть продолжена немедленно, ..." -- Так что вы, вероятно, можете сделать, это в вашем фиктивная реализация, или в DoAndReturn вместо возврата nil в качестве канала из Done() вернуть правильно инициализированный, но закрытый канал; то err = <-ps.Done() больше не должен блокироваться и вместо этого немедленно возвращаться.
mkopriva 08.02.2023 08:43

@mkopriva я также модифицирую другой метод, я пишу последнюю часть статьи, но все равно не работает. Надеюсь, вы запишите код, позвольте мне правильно понять вашу идею.

Y. Ryan 08.02.2023 09:11

Под «возвратом закрытого канала» я имел в виду что-то вроде этого: ch := make(chan error); close(ch); return ch. Если это не разблокирует err = <-ps.Done(), то я не уверен, что будет, я недостаточно знаком с gomock, чтобы точно знать, как заставить его работать.

mkopriva 08.02.2023 09:19

я также добавляю close(ch); затем верните ch, все равно не работает

Y. Ryan 08.02.2023 09:31

Я нашел первопричину. Спасибо!

Y. Ryan 09.02.2023 09:48
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
113
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ниже показан, как мне кажется, минимальный код для реализации целей тестирования.

Он не использует никаких фиктивных фреймворков, потому что, по моему опыту, они, как правило, запутывают цель теста, требуют, чтобы все в команде научились их использовать, и в них нет необходимости, по крайней мере, в Go. Можно также задаться вопросом, что на самом деле тестирует тест...

Во-первых, давайте добавим недостающий производственный код:

type factoryInterface interface {
    CreateVService(ctx context.Context) (PServiceInterface, error)
}

type vService struct {
    factory factoryInterface
}

А теперь тестовый код, состоящий из трех частей: фабрика, макет и тест.

Фабрика испытаний:

type testFactory struct {
    mock PServiceInterface
}

func (f *testFactory) CreateVService(ctx context.Context) (PServiceInterface, error) {
    return f.mock, nil
}

Макет:

type ServiceMock struct {
    records []string
}

func (sm *ServiceMock) Start(reason string) {
    sm.records = append(sm.records, "start")
}

func (sm *ServiceMock) Done() <-chan error {
    sm.records = append(sm.records, "done")
    ch := make(chan error)
    close(ch)
    return ch
}

И, наконец, тест:

func TestWithMock(t *testing.T) {
    mock := ServiceMock{}
    sut := &vService{factory: &testFactory{&mock}}

    err := sut.StartP(context.Background(), "banana")
    if err != nil {
        t.Fatalf("StartP: have: %s; want: no error", err)
    }

    if have, want := len(mock.records), 2; have != want {
        t.Fatalf("number of mock calls: have: %v; want: %v", have, want)
    }

    if have, want := mock.records[0], "start"; have != want {
        t.Fatalf("mock call 1: have: %v; want: %v", have, want)
    }

    if have, want := mock.records[1], "done"; have != want {
        t.Fatalf("mock call 2: have: %v; want: %v", have, want)
    }
}

Три утверждения в последовательности фиктивных вызовов можно свернуть в одно, сравнивая непосредственно срез []string{"start", "done"}, если используется тестовая библиотека, такая как отличный assert пакет https://github.com/gotestyourself/gotest.tools

Ваша концепция идеальна. Я нашел первопричину. Спасибо!

Y. Ryan 09.02.2023 09:46

@ Y.Ryan Если ответ помог вам, рассмотрите возможность его принятия. Это помогает будущим пользователям и дает мне стимул продолжать отвечать на вопросы.

marco.m 09.02.2023 13:10
Ответ принят как подходящий

Я нашел ответ, основная причина в том, что DoAndReturn что-то не так.

Тип функции должен быть <-chan error, а не chan error

 mockProvisionService.EXPECT().Done().Times(1).DoAndReturn( func() <-chan error{
            fmt.Println("DoAndReturn...err nil")
            ch := make(chan error, 1)
            ch <- nil
            return ch
        })

Другие вопросы по теме