如何模拟 amqp.Dial 等库中的函数
How to mock functions from libraries like amqp.Dial
我正在开发一个小型 AMQP 消费者,我想测试我的消费者代码,但我很难模拟 amqp.Dial
。我已经添加了一些接口所以我可以模拟 Connection
和 Channel
并添加了一个 属性 所以我可以控制拨号功能:
//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)
...
这似乎接近我想要达到的目标,我可以这样指定我的测试:
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()
}
但我无法在主例程中将原始 amqp.Dial
作为 dial-func 传递:
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,
}
给予
./main.go:28:9: cannot use amqp.Dial (type func(string) (*amqp.Connection, error)) as type consumer.AmqpDial in field value
我 hoped/thought 因为 amqp.Connection
实现了 AmqpConnection
接口,这会起作用:/
像 amqp.Dial
这样的模拟方法的正确方法是什么?
P.S.: 我知道 https://github.com/NeowayLabs/wabbit 但我更愿意在较低级别上解决这个问题 :)
更新:@mkopriva 建议使用另一种包装方法,所以我尝试了:
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,
}
但这会导致:
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
给定以下类型:
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)
您可以创建委托给实际代码的简单包装器:
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()
}
我正在开发一个小型 AMQP 消费者,我想测试我的消费者代码,但我很难模拟 amqp.Dial
。我已经添加了一些接口所以我可以模拟 Connection
和 Channel
并添加了一个 属性 所以我可以控制拨号功能:
//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)
...
这似乎接近我想要达到的目标,我可以这样指定我的测试:
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()
}
但我无法在主例程中将原始 amqp.Dial
作为 dial-func 传递:
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,
}
给予
./main.go:28:9: cannot use amqp.Dial (type func(string) (*amqp.Connection, error)) as type consumer.AmqpDial in field value
我 hoped/thought 因为 amqp.Connection
实现了 AmqpConnection
接口,这会起作用:/
像 amqp.Dial
这样的模拟方法的正确方法是什么?
P.S.: 我知道 https://github.com/NeowayLabs/wabbit 但我更愿意在较低级别上解决这个问题 :)
更新:@mkopriva 建议使用另一种包装方法,所以我尝试了:
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,
}
但这会导致:
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
给定以下类型:
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)
您可以创建委托给实际代码的简单包装器:
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()
}