Golang how to test function that return channel type?

1.1k Views Asked by At

I try to test function StartP,
Expect that Start() should be called 1 times, Done() should be called 1 times

but I have trouble that test will block when run this step <-ps.Done()

I expect <-ps.Done() return nil

How can I test function that return chan type?

// 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)
}

I use gomock to mock the PServiceInterface

// interface

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

gomock gen this function

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
}

// I also try this

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

        ch := make(chan error, 1)
        ch <- nil
        return ch
    })
2

There are 2 best solutions below

0
Y. Ryan On BEST ANSWER

I found the answer, root cause is DoAndReturn something wrong.

Func type should be <-chan error, not 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
        })
2
marco.m On

The following shows, I think, the minimum code to implement your test goals.

It does not use any mocking framework because in my experience they tend to obfuscate the test intent, require everybody in the team to learn how to use them and are not needed, at least in Go. One could also wonder what the test is actually testing...

First, let's add some missing production code:

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

type vService struct {
    factory factoryInterface
}

And now the test code, in three parts: the factory, the mock, and the test.

The test factory:

type testFactory struct {
    mock PServiceInterface
}

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

The mock:

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
}

And finally the test:

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)
    }
}

The three assertions on the mock call sequence can be collapsed into one, comparing directly the slice []string{"start", "done"}, if one is using a test library such as the excellent assert package of https://github.com/gotestyourself/gotest.tools