Golang A good way to get struct data with gomega matcher

eye-catch Golang

It took me about 20 minutes to know the way to access a property in a struct that is passed by a channel in a unit test. I knew it in a blocking way but didn’t know in a non-blocking way. So let’s learn here how to do it.

Let’s catch up on the basics first with a literal value. And then, look at the code for struct.

Sponsored links

How to get channel data and compare the literal value

A channel can be used in the following way in a unit test.

It("Expect", func() {
    eventChan := make(chan int, 10)

    go func(ch chan int) {
        ch <- 22
    }(eventChan)

    Expect(<-eventChan).To(Equal(22))
})

However, this way completely blocks the thread. The test doesn’t finish if it’s not implemented properly.

Sponsored links

Non blocking way to get channel data

It’s better not to block the thread in a unit test. It can be done with Eventually instead of Expect.

It("Receive", func() {
    eventChan := make(chan int, 10)

    go func(ch chan int) {
        ch <- 22
    }(eventChan)

    Eventually(eventChan).Should(Receive())
})

We can set a timeout by WithTimeout if necessary. The combination of Eventually and Receive improves the situation above but this test doesn’t check the value itself. We need an additional statement for the value comparison.

Receive function accepts as many gomega matchers as we need. In this case, we can put Equal in Receive function.

It("Receive with Equal", func() {
    eventChan := make(chan int, 10)

    go func(ch chan int) {
        ch <- 22
    }(eventChan)

    Eventually(eventChan).Should(Receive(Equal(22)))
})

The property can’t be read in Receive function

Let’s write a test for struct. The struct is simple enough. The test can be written in the same way as literal.

type Event struct {
    Name  string
    Value string
}

It("Receive with Equal", func() {
    eventChan := make(chan Event, 10)

    go func(ch chan Event) {
        ch <- Event{
            Name:  "Running",
            Value: "dummy data",
        }
    }(eventChan)

    Eventually(eventChan).Should(Receive(Equal(Event{
        Name:  "Running",
        Value: "dummy data",
    })))
})

As you can see above, Equal function can be used here. However, if the struct has a pointer property or struct in it, the comparison result becomes false. A property in the struct can’t be read directly in this way. If we use Expect here, it will be a blocking call.

Pass the channel data to another variable

I found an example on the official page.

var receivedBagel Bagel
Eventually(bagelChan).Should(Receive(&receivedBagel))
Ω(receivedBagel.Contents()).Should(ContainElement("cream cheese"))
Ω(receivedBagel.Kind()).Should(Equal("sesame"))
https://onsi.github.io/gomega/#receive

It seems to be possible to get the data by passing an address to Receive function.

It("Receive and get struct data", func() {
    eventChan := make(chan Event, 10)

    go func(ch chan Event) {
        ch <- Event{
            Name:  "Running",
            Value: "dummy data",
        }
    }(eventChan)

    var receivedEvent Event
    Eventually(eventChan).Should(Receive(&receivedEvent))
    Expect(receivedEvent.Name).To(Equal("Running"))
    Expect(receivedEvent.Value).To(Equal("dummy data"))
})

In this way, we can read the desired property directly and put fine-grained conditions in the unit test.

For example, we can put different conditions in the following way.

It("Receive and get struct data", func() {
    eventChan := make(chan Event, 10)

    go func(ch chan Event) {
        ch <- Event{
            Name:  "Running",
            Value: "dummy data",
        }
    }(eventChan)

    var receivedEvent Event
    Eventually(eventChan).Should(Receive(&receivedEvent))
    Expect(receivedEvent.Name).To(Equal("Running"))
    Expect(receivedEvent.Value).To(Equal("dummy data"))

    Expect(receivedEvent.Value).To(And(
        ContainSubstring("dummy"),
        ContainSubstring("data"),
    ))
    Expect(receivedEvent.Value).To(Or(
        ContainSubstring("dummy"),
        ContainSubstring("-------"),
    ))
    Expect(receivedEvent.Value).To(Or(
        ContainSubstring("-----"),
        ContainSubstring("data"),
    ))
})

Receive channel data multiple times

We might need to write a test case where the channel sends data multiple times. Just repeat the statement as many as you want.

It("Receive data multiple time", func() {
    eventChan := make(chan Event, 10)

    go func(ch chan Event) {
        ch <- Event{
            Name:  "Running",
            Value: "dummy data",
        }
        ch <- Event{
            Name:  "Stop",
            Value: "Let's take a break",
        }
    }(eventChan)

    var receivedEvent Event
    Eventually(eventChan).Should(Receive(&receivedEvent))
    Expect(receivedEvent.Name).To(Equal("Running"))
    Expect(receivedEvent.Value).To(Equal("dummy data"))

    Eventually(eventChan).Should(Receive(&receivedEvent))
    Expect(receivedEvent.Name).To(Equal("Stop"))
    Expect(receivedEvent.Value).To(ContainSubstring("take a break"))
})

In this way, we can control the order of the data.

Test Related Article

Comments

Copied title and URL