Skip to content

Latest commit

 

History

History
157 lines (134 loc) · 5.8 KB

messaging-pacts.md

File metadata and controls

157 lines (134 loc) · 5.8 KB

Messaging Pacts

Messaging pacts work similarly to synchronous request/response pacts in that there is still a consumer and a producer. The consumer defines the messages it expects to receive (including metadata and the message contents) and the producer is verified to ensure that the messages it produces meet those expectations.

It's important to ensure that any given consumer or provider name doesn't have both request/response and messaging pacts. If you have a HTTP API which also sends and/or receives messages, make sure the two different types use two different names, for example "Stock Broker API" and "Stock Broker Messaging".

Sample

See the sample for additional detail.

Consumer Tests

Consumer tests are very similar to request/response pacts. Your consumer specifies which messages it wishes to receive and PactNet generates a pact file which contains all of the specified interactions.

In code, this is:

public class StockEventProcessorTests
{
    private readonly IMessagePactBuilderV3 messagePact;

    public StockEventProcessorTests()
    {
        IMessagePactV3 v3 = MessagePact.V3("Stock Event Consumer", "Stock Event Producer", new PactConfig
        {
            // the location in which the pact file is written
            PactDir = "../../../pacts/",

            // the settings used to serialise each message by default
            DefaultJsonSettings = new JsonSerializerSettings
            {
                ContractResolver = new CamelCasePropertyNamesContractResolver()
            }
        });

        this.messagePact = v3.UsingNativeBackend();
    }

    [Fact]
    public void RecieveSomeStockEvents()
    {
        // define your expected message, using matchers for the message body
        this.messagePact
            .ExpectsToReceive("some stock ticker events")
            .WithMetadata("key", "value")
            .WithJsonContent(Match.MinType(new
            {
                Name = Match.Type("AAPL"),
                Price = Match.Decimal(1.23m)
            }, 1))
            .Verify<ICollection<StockEvent>>(events =>
            {
                // here we simply make sure it's expected, but we could run it through a real event processor
                // to make sure it was processed properly. Warning - this is not meant for integration testing!
                events.Should().BeEquivalentTo(new[]
                {
                    new StockEvent
                    {
                        Name = "AAPL",
                        Price = 1.23m
                    }
                });
            });
    }

After all of your consumer tests have passed a message pact file is written to disk. This file will be used during the provider verification tests.

Provider Tests

Provider tests look very similar to request/response pacts, but with one big difference; you must register a handler for each interaction which generates a sample message.

The key difference for mesaging pacts is that the transport used is not via HTTP. It would be unreasonable for PactNet to attempt to implement all the different transports that these messages could use - such as Kafka, RabbitMQ, ZeroMQ, etc - and so internally the messages are simulated during the provider verification stage. This is done transparently and so you don't need to worry about how this is achieved.

In code, this is:

public class StockEventGeneratorTests : IDisposable
{
    private readonly PactVerifier verifier;

    public StockEventGeneratorTests()
    {
        this.verifier = new PactVerifier();
    }

    public void Dispose()
    {
        // make sure you dispose the verifier to stop the internal messaging server
        GC.SuppressFinalize(this);
        this.verifier.Dispose();
    }

    [Fact]
    public void EnsureEventApiHonoursPactWithConsumer()
    {
        string pactPath = Path.Combine("..",
                                       "..",
                                       "..",
                                       "..",
                                       "Consumer.Tests",
                                       "pacts",
                                       "Stock Event Consumer-Stock Event Producer.json");

        var defaultSettings = new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver(),
            DefaultValueHandling = DefaultValueHandling.Ignore,
            NullValueHandling = NullValueHandling.Ignore,
            Formatting = Formatting.Indented
        };

        this.verifier
            .MessagingProvider("Stock Event Producer", defaultSettings)
            .WithProviderMessages(scenarios =>
             {
                 // register the responses to each interaction
                 // the descriptions must match those in the pact file(s)
                 scenarios.Add("a single event", () => new StockEvent
                          {
                              Name = "AAPL",
                              Price = 1.23m
                          })
                          .Add("some stock ticker events", builder =>
                          {
                              builder.WithMetadata(new
                                     {
                                         ContentType = "application/json",
                                         Key = "value"
                                     })
                                     .WithContent(new[]
                                     {
                                         new StockEvent { Name = "AAPL", Price = 1.23m },
                                         new StockEvent { Name = "TSLA", Price = 4.56m }
                                     });
                          });
             })
            .WithFileSource(new FileInfo(pactPath))
            .Verify();
    }
}