testing and mocking pubsub subscription and channel in golang

Viewed 236

I am trying to learn go, I have a Java background. What I am trying to accomplish is the ability to mock pubsub and when a message is received a channel sends a message.

What I am finding hard is the fact that the method has no params. I have to Mock pubsub Receive and in that method send a mock message over a channel.

when I run

go test ./... --cover

I currently get 0%

method to test

func (s *Service) Consume() error {
    subscription := os.Getenv("OUTO_PUBSUB_SUBSCRIPTION")
    sub := s.Client.Subscription(subscription)

    ctx := context.Background()

    err := sub.Receive(ctx, func(ctx context.Context, msg *pubsub.Message) {
        msg.Ack()
        fmt.Println(msg.Data)
        s.messageChannel <- string(msg.Data)
    })

    if err != nil {
        fmt.Println(err)
        return fmt.Errorf("sub.Receive: %v", err)
    }
    return nil
}

pubsub test

func TestConsume(t *testing.T) {
    // create channels
    testChan := make(chan string, 100)
    service := Service{messageChannel: testChan}
    sending := service.messageChannel
    sending <- "Test message"

    t.Run("Test when consume runs a message is sent via channel", func(t *testing.T) {
        got := <-sending
        want := "Test message"
        if want != got {
            t.Fatalf("wanted %v, got %v", want, got)
        }
    })

}

I am able to get the channel test to work but I need to call the Consume method so the go framework knows I am actually testing against it.

advice?


Answer provided but run into an issue mocking Pubsub client. When implementing the mock client it just hangs and the test never finishes and when using the MockClient struct the IDE complain it is not type of pubsub.Client

type MockClient struct {
}

type MockSubscription struct {
}

func (mc MockClient) Subscription() MockSubscription {
    return MockSubscription{}
}

func (ms MockSubscription) Receive(ctx context.Context, f func(context.Context, *pubsub.Message)) error {
    /* create the message you're mocking you're receiving here */
    /* you might have to mock the message struct and its interface if you want to validate that it has be ACKed */
    msg := new(pubsub.Message)
    f(ctx, msg)
    // return an error or not i'll be returning nil for now
    return nil
}

func TestConsume(t *testing.T) {

    conn, err := grpc.Dial("myMockServerAddress", grpc.WithInsecure())
    if err != nil {
        // TODO: Handle the error
    }
    ctx := context.Background()
    // Now create the pubsub client with the grpc conn
    client, err := pubsub.NewClient(ctx, "mockProject", option.WithGRPCConn(conn))

    // create channels
    testChan := make(chan string, 100)
    //when MockClient{} is here a error occurs Cannot use 'MockClient{}' (type MockClient) as the type *pubsub.Client
    service := Service{messageChannel: testChan, Client: client}
    sending := service.messageChannel
    sending <- "Test message"

    t.Run("Test when consume runs a message is sent via channel", func(t *testing.T) {
        service.Consume() // run the function so you can test it
        got := <-sending
        want := "Test message"
        if want != got {
            t.Fatalf("wanted %v, got %v", want, got)
        }
    })

}
1 Answers

It's a bit tedious but you'd have to create a mock of your Service struct and it's associated interface. Below is an example, you may have to change it to match the interface properly.

type MockClient struct {
}

type MockSubscription struct {
}

func (mc MockClient) Subscription(sub string) {
    return MockSubscription{}
}

func (ms MockSubscription) Receive(ctx context.Context,f func(context.Context, *pubsub.Message)) error {
    /* create the message you're mocking you're receviing here */
    /* you might have to mock the message struct and its interface if you want to validate that it has be ACKed */
    msg := new(pubsub.Message)
    f(ctx, msg)
    // return an error or not i'll be returning nil for now
    return nil
}


func TestConsume(t *testing.T) {
    // create channels
    testChan := make(chan string, 100)
    service := Service{messageChannel: testChan, Client: MockClient{}}
    sending := service.messageChannel
    sending <- "Test message"

    t.Run("Test when consume runs a message is sent via channel", func(t *testing.T) {
            service.Consume() // run the function so you can test it
            got := <-sending
            want := "Test message"
            if want != got {
                    t.Fatalf("wanted %v, got %v", want, got)
            }
    })

}
Related