I'm working on a small AMQP consumer and i want to test my consumer code, but im struggleing to mock the amqp.Dial
. I have added some interface so i can mock Connection
and Channel
and added a property so i can control the dial-function:
//consumer.go
type AmqpChannel interface {
ExchangeDeclare(name, kind string, durable, autoDelete, internal, noWait bool, args amqp.Table) error
QueueDeclare(name string, durable, autoDelete, exclusive, noWait bool, args amqp.Table) (amqp.Queue, error)
QueueBind(name, key, exchange string, noWait bool, args amqp.Table) error
Consume(queue, consumer string, autoAck, exclusive, noLocal, noWait bool, args amqp.Table) (<-chan amqp.Delivery, error)
Publish(exchange, key string, mandatory, immediate bool, msg amqp.Publishing) error
}
type AmqpConnection interface {
Channel() (AmqpChannel, error)
Close() error
}
type AmqpDial func(url string) (AmqpConnection, error)
type MyConsumer struct {
HostDsn string
channel AmqpChannel
queue amqp.Queue
connection AmqpConnection
DialFunc AmqpDial
}
func (c *MyConsumer) Connect() error {
var err error
c.connection, err = c.DialFunc(c.HostDsn)
...
This seems to be close to what i want to achive, i can specify my test like this:
func TestConsumer(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
var myConsumer = consumer.MyConsumer{
HostDsn: "test",
DialFunc: func(url string) (consumer.AmqpConnection, error) {
return mocks.NewMockAmqpConnection(mockCtrl), nil
},
}
_ = myConsumer.Connect()
}
But i cannot pass the original amqp.Dial
as dial-func on the main routine:
myConsumer := consumer.MyConsumer{
HostDsn: fmt.Sprintf(
"amqp://%s:%s@rabbitmq:5672/?heartbeat=5s",
os.Getenv("RABBITMQ_USER"),
url.QueryEscape(os.Getenv("RABBITMQ_PASSWORD")),
),
DialFunc: amqp.Dial,
}
gives
./main.go:28:9: cannot use amqp.Dial (type func(string) (*amqp.Connection, error)) as type consumer.AmqpDial in field value
I hoped/thought that, as amqp.Connection
fulfills the AmqpConnection
interface, this would work :/
What is the proper way of mocking methods like amqp.Dial
?
P.S.: im aware of https://github.com/NeowayLabs/wabbit but i would prefer to solve this on a lower level :)
Update: @mkopriva suggested to use another wrapper method, so i tried:
func getDialer(url string) (consumer.AmqpConnection, error) {
var connection, err = amqp.Dial(url)
return connection, err
}
myConsumer := consumer.myConsumer{
HostDsn: fmt.Sprintf(
"amqp://%s:%s@rabbitmq:5672/?heartbeat=5s",
os.Getenv("RABBITMQ_USER"),
url.QueryEscape(os.Getenv("RABBITMQ_PASSWORD")),
),
DialFunc: getDialer,
}
But this results in:
cannot use connection (type *amqp.Connection) as type consumer.AmqpConnection in return argument:
*amqp.Connection does not implement consumer.AmqpConnection (wrong type for Channel method)
have Channel() (*amqp.Channel, error)
want Channel() (consumer.AmqpChannel, error)
make: *** [Makefile:4: build-consumer] Error 2
Given the following types:
type AmqpChannel interface {
ExchangeDeclare(name, kind string, durable, autoDelete, internal, noWait bool, args amqp.Table) error
QueueDeclare(name string, durable, autoDelete, exclusive, noWait bool, args amqp.Table) (amqp.Queue, error)
QueueBind(name, key, exchange string, noWait bool, args amqp.Table) error
Consume(queue, consumer string, autoAck, exclusive, noLocal, noWait bool, args amqp.Table) (<-chan amqp.Delivery, error)
Publish(exchange, key string, mandatory, immediate bool, msg amqp.Publishing) error
}
type AmqpConnection interface {
Channel() (AmqpChannel, error)
Close() error
}
type AmqpDial func(url string) (AmqpConnection, error)
You can create simple wrappers that delegate to the actual code:
func AmqpDialWrapper(url string) (AmqpConnection, error) {
conn, err := amqp.Dial(url)
if err != nil {
return nil, err
}
return AmqpConnectionWrapper{conn}, nil
}
type AmqpConnectionWrapper struct {
conn *amqp.Connection
}
// If *amqp.Channel does not satisfy the consumer.AmqpChannel interface
// then you'll need another wrapper, a AmqpChannelWrapper, that implements
// the consumer.AmqpChannel interface and delegates to *amqp.Channel.
func (w AmqpConnectionWrapper) Channel() (AmqpChannel, error) {
return w.conn.Channel()
}
func (w AmqpConnectionWrapper) Close() error {
return w.conn.Close()
}