GitHub
06/28/2023, 8:44 AMPactVerifier
API enforces either verifying HTTP-style pacts or messaging-style pacts, but not both:
var verifier = new PactVerifier();
// HTTP style
verifier.ServiceProvider("My API", new Uri("<http://localhost:5000>"))
.WithFileSource(...)
.Verify();;
// Messaging style
verifier.MessagingProvider("My API")
.WithProviderMessages(scenarios => { ... })
.WithFileSource(...)
.Verify();;
This means that any v4 pacts containing both HTTP and messaging interactions will fail to verify, because you can't specify both a HTTP and messaging provider on the verifier. Even though the v4 pacts themselves support shared pacts, PactNet 4.x does not.
Proposal
A breaking change is made to PactNet 5.x such that you can specify both a HTTP and messaging 'transport' (which is what the Pact FFI calls them internally):
var verifier = new PactVerifier("My API");
verifier
.WithHttpEndpoint(new Uri("<http://localhost:5000>"))
.WithMessages(scenarios =>
{
scenarios.Add<MyEvent>("an event happens")
})
.WithFileSource(new FileInfo(@"..."))
.Verify();
The breaking changes are:
• PactVerifier
requires the provider name as a mandatory constructor parameter (otherwise it would have to be specified on both the HTTP and message transport, and they'd have to match)
• ServiceProvider
is renamed to WithHttpEndpoint
and the provider name parameter is removed
• MessagingProvider
is renamed to WithMessages
, the provider name parameter is removed and the messages are supplied directly instead of via an additional call to WithProviderMessages
• This means IPactVerifierMessagingProvider
is no longer needed and is deleted
• All the WithXSource
methods are moved to IPactVerifier
• This means IPactVerifierProvider
is no longer needed and is deleted
Caveats/Drawbacks
Both WithHttpEndpoint
and WithMessages
are optional in case a particular service doesn't have both types of interactions, but at least one must be provided and at most one of each type. I would ideally like these invariants to be enforced at compile time but that isn't really possible without introducing a very awkward type hierarchy. Consider:
• After you add a HTTP transport you can either add a message transport also or move directly to specifying a source
• If you add a message transport, you must move directly to adding a source
• After you add a message transport you can either add a HTTP transport also or move directly to specifying a source
• If you add a HTTP transport, you must move directly to adding a source
This means you have really awkward types like:
public interface IPactVerifier
{
public IHttpAddedButNotMessageYet WithHttpEndpoint();
public IMessageAddedButNotHttpYet WithMessages();
}
public interface IMessageAddedButNotHttpYet : IPactVerifierTransportsConfigured
{
// 👇 this is a duplicate of the method above in terms of signature, but with different return type
public IPactVerifierTransportsConfigured WithHttpEndpoint();
}
public interface IHttpAddedButNotMessageYet : IPactVerifierTransportsConfigured
{
// 👇 this is a duplicate of the method above in terms of signature, but with different return type
public IPactVerifierTransportsConfigured WithMessages();
}
public interface IPactVerifierTransportsConfigured
{
public IPactVerifierSource WithFileSource(...);
public IPactVerifierSource WithDirectorySource(...);
// etc
}
I don't think the added complexity to the types is worth the added runtime safety in this instance, particular given this is a testing library and we can simply throw InvalidOperationException
if you try to add the same transport twice, or no transports at all. The feedback loop is fast, the error is easy to communicate/fix, and the code isn't production code.
It also creates a combinatorial explosion if in future we support more transport types.
pact-foundation/pact-netGitHub
06/28/2023, 8:44 AM