Unit testing AMQP/build flags for tests

819 views
Skip to first unread message

Shane H

unread,
Nov 19, 2019, 12:00:26 AM11/19/19
to golang-nuts
I'm trying to unit test some code (pasted below). I've struggled to find a way to mock the amqp.Connection, so have decided to go the monkey patching route.

The test 'works' but only if I use the following incantation 
go test -gcflags=-l

So, my next step is to ensure that -gcflags is set to prevent inlining when this test is executed. 
My /thought/ was to check os.Getenv("GO_GCFLAGS"), but as I was kindly reminded, executng the tests and compiling the tests are two different things.
My next brilliant idea was to use a build tag to ensure that the test file is only built when the flags are set as I desire. But https://golang.org/pkg/go/build/ doesn't offer any ideas where gcflags might be checked, or what tag to use.

So, I throw myself at the mercy of golang-nuts to (gently) point me in the right direction (I completely understand that there may be an easier way to do this, but I cannot see that path)

```
### Code to test
// MQ -
type MQ struct {
conn  *amqp.Connection
Retry int
URI   string
}

// Connect -
func (mq *MQ) Connect() (err error) {
// Retry MUST be > 0
if mq.Retry == 0 {
log.Print("Cannot use a Retry of zero, this process will to default retry to 1")
mq.Retry = 1
}

// Note: Even though amqp.ParseURI(uri) will validate the URI formed, check here that the minimum required exists
if mq.URI == "" {
log.Printf("No Message Queue URI configured")
}

for {
for i := 0; i < mq.Retry; i++ {
mq.conn, err = amqp.Dial(mq.URI)
if err == nil {
// Successful connection
log.Printf("Successfully connected to RabbitMQ")
return nil
}
time.Sleep(1 * time.Second)
}
// Log that there is a problem connecting to the RabbitMQ service that needs urgent attention
backoff := time.Duration(mq.Retry*rand.Intn(10)) * time.Second
log.Printf("ALERT: Trouble connecting to RabbitMQ, error: %v, going to re-enter retry loop in %s seconds", err, backoff.String())
time.Sleep(backoff)
}
}

### Test Code
func TestConnect(t *testing.T) {
testcases := map[string]struct {
retry int
uri   string
err   error
}{
"Happy Path": {retry: 1, uri: "amqp://localhost:5672/%2f"},
}
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
// Monkeypatch amqp to return the nil and the error
fakeRabbitConnection := func(msg string) (*amqp.Connection, error) {
return nil, tc.err // I only want the error to have meaning, therefore the connection can be nil (which also saves me having to create a mock)
}
patch := monkey.Patch(amqp.Dial, fakeRabbitConnection)
defer patch.Unpatch()
mq := rabbit.MQ{Retry: tc.retry, URI: tc.uri}
output := mq.Connect()
fmt.Println(output) // TODO
})
}
}
```

burak serdar

unread,
Nov 19, 2019, 12:11:52 AM11/19/19
to Shane H, golang-nuts
On Mon, Nov 18, 2019 at 10:00 PM Shane H <shan...@gmail.com> wrote:
>
> I'm trying to unit test some code (pasted below). I've struggled to find a way to mock the amqp.Connection, so have decided to go the monkey patching route.
>
> The test 'works' but only if I use the following incantation
> go test -gcflags=-l
>
> So, my next step is to ensure that -gcflags is set to prevent inlining when this test is executed.
> My /thought/ was to check os.Getenv("GO_GCFLAGS"), but as I was kindly reminded, executng the tests and compiling the tests are two different things.
> My next brilliant idea was to use a build tag to ensure that the test file is only built when the flags are set as I desire. But https://golang.org/pkg/go/build/ doesn't offer any ideas where gcflags might be checked, or what tag to use.
>
> So, I throw myself at the mercy of golang-nuts to (gently) point me in the right direction (I completely understand that there may be an easier way to do this, but I cannot see that path)


This is what I usually do in these situations:

var amqpDial=amqp.Dial
func (mq *MQ) Connect() (err error) {
...
mq.conn, err = amqpDial(mq.URI)
...
}

func TestConnect(t *testing.T) {
amqpDial=fakeDial
defer func() {amqpDial=amqp.Dial}()
...
}


>
> ```
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/133174cd-e5ef-4e65-bfe0-e626914803bb%40googlegroups.com.

Shane H

unread,
Nov 19, 2019, 12:23:50 AM11/19/19
to golang-nuts


On Tuesday, November 19, 2019 at 4:11:52 PM UTC+11, burak serdar wrote:


This is what I usually do in these situations:

var amqpDial=amqp.Dial
 func (mq *MQ) Connect() (err error) {
  ...
   mq.conn, err = amqpDial(mq.URI)
  ...
}

func TestConnect(t *testing.T) {
    amqpDial=fakeDial
    defer func() {amqpDial=amqp.Dial}()
   ...
}


This works, the only drawback is that I have to choose between making  amqpDial an exported (I want to say symbol here) variable, or have the test in the same package (as opposed to a _test pkg)

I've gone with the latter, put these tests into the same package, thanks.

burak serdar

unread,
Nov 19, 2019, 12:38:03 AM11/19/19
to Shane H, golang-nuts
Instead of exporting the variable, you can export a setter function,
something like:

func SetAMQPDialerForTest(f func) {
...
}

The name is explicit, it can only be used for setting, and iirc, the
linker removes it when it is not used (i.e. when not testing).

>
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/d6a6e01c-00df-47f7-94ac-a37e00cb73d7%40googlegroups.com.

Sebastien Binet

unread,
Nov 19, 2019, 3:30:23 AM11/19/19
to burak serdar, Shane H, golang-nuts
On Tue, Nov 19, 2019 at 6:37 AM burak serdar <bse...@computer.org> wrote:
On Mon, Nov 18, 2019 at 10:24 PM Shane H <shan...@gmail.com> wrote:
>
>
>
> On Tuesday, November 19, 2019 at 4:11:52 PM UTC+11, burak serdar wrote:
>>
>>
>>
>> This is what I usually do in these situations:
>>
>> var amqpDial=amqp.Dial
>>  func (mq *MQ) Connect() (err error) {
>>   ...
>>    mq.conn, err = amqpDial(mq.URI)
>>   ...
>> }
>>
>> func TestConnect(t *testing.T) {
>>     amqpDial=fakeDial
>>     defer func() {amqpDial=amqp.Dial}()
>>    ...
>> }
>>
>>
> This works, the only drawback is that I have to choose between making  amqpDial an exported (I want to say symbol here) variable, or have the test in the same package (as opposed to a _test pkg)
>
> I've gone with the latter, put these tests into the same package, thanks.

you could have your cake *and* eat it:

$> cat foo_test.go
package foo

func SetDialer(d Dialer) {
   dialer = d
}

$> cat all_test.go
package foo_test

func TestMain(m *testing.M) {
    foo.SetDialer(fakeDialer)
    os.Exit(m.Run())
}

ie: only export that API for tests.

hth,
-s
Reply all
Reply to author
Forward
0 new messages