测试Go中的Nats订阅

huangapple go评论96阅读模式
英文:

Testing Nats subscription in Go

问题

我有一个函数设计用来监听 Nats 主题并在接收到消息时路由它们:

  1. func (conn *JetStreamConnection) SubscribeMultiple(ctx context.Context, subject string,
  2. subscribers ...*SubscriptionCallback) error {
  3. callbacks := make(map[string]func(*pnats.NatsMessage) (func(context.Context), error))
  4. for _, subscriber := range subscribers {
  5. callbacks[subscriber.Category] = subscriber.Callback
  6. }
  7. fullSubject := fmt.Sprintf("%s.*", subject)
  8. sub, err := conn.context.SubscribeSync(fullSubject, nats.Context(ctx))
  9. if err != nil {
  10. return err
  11. }
  12. loop:
  13. for {
  14. select {
  15. case <-ctx.Done():
  16. break loop
  17. default:
  18. }
  19. msg, err := sub.NextMsgWithContext(ctx)
  20. if err != nil {
  21. return err
  22. }
  23. msg.InProgress()
  24. var message pnats.NatsMessage
  25. if err := conn.unmarshaller(msg.Data, &message); err != nil {
  26. msg.Term()
  27. return err
  28. }
  29. actualSubject := fmt.Sprintf("%s.%s", subject, message.Context.Category)
  30. subscriber, ok := callbacks[message.Context.Category]
  31. if !ok {
  32. msg.Nak()
  33. continue
  34. }
  35. callback, err := subscriber(&message)
  36. if err == nil {
  37. msg.Ack()
  38. } else {
  39. msg.Nak()
  40. return err
  41. }
  42. callback(ctx)
  43. }
  44. if err := sub.Unsubscribe(); err != nil {
  45. return err
  46. }
  47. return nil
  48. }

我的问题是,由于 SubscribeSync 函数生成了一个 *nats.Subscription 对象,我无法对测试进行模拟。我该如何在这个对象周围进行测试?

英文:

I have a function designed to listen to a Nats subject and route the messages as it receives them:

  1. func (conn *JetStreamConnection) SubscribeMultiple(ctx context.Context, subject string,
  2. subscribers ...*SubscriptionCallback) error {
  3. callbacks := make(map[string]func(*pnats.NatsMessage) (func(context.Context), error))
  4. for _, subscriber := range subscribers {
  5. callbacks[subscriber.Category] = subscriber.Callback
  6. }
  7. fullSubject := fmt.Sprintf(&quot;%s.*&quot;, subject)
  8. sub, err := conn.context.SubscribeSync(fullSubject, nats.Context(ctx))
  9. if err != nil {
  10. return err
  11. }
  12. loop:
  13. for {
  14. select {
  15. case &lt;-ctx.Done():
  16. break loop
  17. default:
  18. }
  19. msg, err := sub.NextMsgWithContext(ctx)
  20. if err != nil {
  21. return err
  22. }
  23. msg.InProgress()
  24. var message pnats.NatsMessage
  25. if err := conn.unmarshaller(msg.Data, &amp;message); err != nil {
  26. msg.Term()
  27. return err
  28. }
  29. actualSubject := fmt.Sprintf(&quot;%s.%s&quot;, subject, message.Context.Category)
  30. subscriber, ok := callbacks[message.Context.Category]
  31. if !ok {
  32. msg.Nak()
  33. continue
  34. }
  35. callback, err := subscriber(&amp;message)
  36. if err == nil {
  37. msg.Ack()
  38. } else {
  39. msg.Nak()
  40. return err
  41. }
  42. callback(ctx)
  43. }
  44. if err := sub.Unsubscribe(); err != nil {
  45. return err
  46. }
  47. return nil
  48. }

My problem is that, since the SubscribeSync function produces a *nats.Subscription object, I have no way to mock out the test. How can I test around this object?

答案1

得分: 2

你可以将循环放在一个单独的函数中。这个函数可以接受一个描述nats Subscription的接口,而不是*nats.Subscription。这样你就可以使用gomock或其他工具创建Subscription的模拟对象。然后你可以单独测试内部函数。

类似这样的代码:

  1. func (conn *JetStreamConnection) SubscribeMultiple(ctx context.Context, subject string,
  2. subscribers ...*SubscriptionCallback) error {
  3. callbacks := make(map[string]func(*pnats.NatsMessage) (func(context.Context), error))
  4. for _, subscriber := range subscribers {
  5. callbacks[subscriber.Category] = subscriber.Callback
  6. }
  7. fullSubject := fmt.Sprintf("%s.*", subject)
  8. sub, err := conn.context.SubscribeSync(fullSubject, nats.Context(ctx))
  9. if err != nil {
  10. return err
  11. }
  12. return run(ctx, sub)
  13. }
  14. //go:generate mockgen -source conn.go -destination ../mocks/conn.go -package mocks
  15. type ISubscription interface{
  16. NextMsgWithContext(ctx context.Context) (*nats.Msg, error)
  17. Unsubscribe() error
  18. }
  19. func (conn *JetStreamConnection) run(ctx context.Context, sub ISubscription) error {
  20. loop:
  21. for {
  22. select {
  23. case <-ctx.Done():
  24. break loop
  25. default:
  26. }
  27. msg, err := sub.NextMsgWithContext(ctx)
  28. if err != nil {
  29. return err
  30. }
  31. msg.InProgress()
  32. var message pnats.NatsMessage
  33. if err := conn.unmarshaller(msg.Data, &message); err != nil {
  34. msg.Term()
  35. return err
  36. }
  37. actualSubject := fmt.Sprintf("%s.%s", subject, message.Context.Category)
  38. subscriber, ok := callbacks[message.Context.Category]
  39. if !ok {
  40. msg.Nak()
  41. continue
  42. }
  43. callback, err := subscriber(&message)
  44. if err == nil {
  45. msg.Ack()
  46. } else {
  47. msg.Nak()
  48. return err
  49. }
  50. callback(ctx)
  51. }
  52. if err := sub.Unsubscribe(); err != nil {
  53. return err
  54. }
  55. }

更新:如果你仍然想测试SubscribeMultiple函数,你可以创建一个只有一个Run函数的Runner结构体,并将其作为JetStreamConnection的依赖项。同样,你可以为Runner创建一个模拟对象,并使用它进行测试。

英文:

You can put your loop in a separate function. This func can accept an interface that describes nats Subscription instead of *nats.Subscription. This way you will be able to create Subscription mocks with gomock or other tools. After that you can test the inside func separately

Something like this:

  1. func (conn *JetStreamConnection) SubscribeMultiple(ctx context.Context, subject string,
  2. subscribers ...*SubscriptionCallback) error {
  3. callbacks := make(map[string]func(*pnats.NatsMessage) (func(context.Context), error))
  4. for _, subscriber := range subscribers {
  5. callbacks[subscriber.Category] = subscriber.Callback
  6. }
  7. fullSubject := fmt.Sprintf(&quot;%s.*&quot;, subject)
  8. sub, err := conn.context.SubscribeSync(fullSubject, nats.Context(ctx))
  9. if err != nil {
  10. return err
  11. }
  12. return run(ctx, sub)
  13. }
  14. //go:generate mockgen -source conn.go -destination ../mocks/conn.go -package mocks
  15. type ISubscription interface{
  16. NextMsgWithContext(ctx context.Context) (*nats.Msg, error)
  17. Unsubscribe() error
  18. }
  19. func (conn *JetStreamConnection) run(ctx context.Context, sub ISubscription) error {
  20. loop:
  21. for {
  22. select {
  23. case &lt;-ctx.Done():
  24. break loop
  25. default:
  26. }
  27. msg, err := sub.NextMsgWithContext(ctx)
  28. if err != nil {
  29. return err
  30. }
  31. msg.InProgress()
  32. var message pnats.NatsMessage
  33. if err := conn.unmarshaller(msg.Data, &amp;message); err != nil {
  34. msg.Term()
  35. return err
  36. }
  37. actualSubject := fmt.Sprintf(&quot;%s.%s&quot;, subject, message.Context.Category)
  38. subscriber, ok := callbacks[message.Context.Category]
  39. if !ok {
  40. msg.Nak()
  41. continue
  42. }
  43. callback, err := subscriber(&amp;message)
  44. if err == nil {
  45. msg.Ack()
  46. } else {
  47. msg.Nak()
  48. return err
  49. }
  50. callback(ctx)
  51. }
  52. if err := sub.Unsubscribe(); err != nil {
  53. return err
  54. }
  55. }

upd: if you still want to test SubscribeMultiple, you can create a Runner that will have only one func Run and take it as dependency for JetStreamConnection. Again, you can create a mock for Runner and test with it

huangapple
  • 本文由 发表于 2022年8月4日 15:06:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/73231636.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定