Pracuję nad małym konsumentem AMQP i chcę przetestować mój kod konsumencki, ale mam zamiar zmusić do skobienie amqp.Dial. Dodałem jakiś interfejs, więc mogę się udać Connection i Channel i dodałem nieruchomość, dzięki czemu mogę kontrolować funkcję Dial:

//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)
...

Wydaje się, że jest to blisko tego, co chcę osiągnąć, mogę określić taki test:

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()
}

Ale nie mogę przekazać oryginału amqp.Dial jako Dial-Func na głównej rutynie:

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,
    }

Daje

./main.go:28:9: cannot use amqp.Dial (type func(string) (*amqp.Connection, error)) as type consumer.AmqpDial in field value

Miałem nadzieję / pomyślałem, że amqp.Connection spełnia interfejs AmqpConnection, to działało: / Jaki jest właściwy sposób szykowanych metod, takich jak amqp.Dial?

PS: Jestem świadomy https://github.com/neowaylabs/wabbit, ale wolałbym rozwiązać to na niższym poziomie :)

AKTUALIZACJA: @MKOPriva Sugeruje się, aby użyć innej metody opakowania, więc próbowałem:

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,
    }

Ale to skutkuje:

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
0
leberknecht 23 październik 2020, 16:37

1 odpowiedź

Najlepsza odpowiedź

Biorąc pod uwagę następujące typy:

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)

Możesz tworzyć proste opakowania, które delegują do rzeczywistego kodu:

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()
}
1
mkopriva 24 październik 2020, 13:07