pact-foundation / pact-net

.NET version of Pact. Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.
https://pact.io
MIT License
823 stars 225 forks source link

Pact verification with CamelCase model names #476

Closed Nick-Sullivan closed 9 months ago

Nick-Sullivan commented 9 months ago

I'm having trouble verifying a pact when the models use CamelCase structure, using pact-net version 4.5.0.

The consumer runs successfully if I use the CamelCasePropertyNamesContractResolver, and generates a pact with lowercase model names, but the provider verification fails. The provider passes if I use lowercase model names. Is there a way to maintain capitalisation in the pact, or make the provider verification ignore capitalisation?

Consumer snippet

var config = new PactConfig
{
    PactDir = $"{Directory.GetParent(Directory.GetCurrentDirectory()).Parent.Parent.Parent.Parent.FullName}{Path.DirectorySeparatorChar}pacts",
    LogLevel = PactLogLevel.Debug,
    DefaultJsonSettings = new JsonSerializerSettings
    {
        ContractResolver = new CamelCasePropertyNamesContractResolver(),  // Consumer passes because of this
    }
};

var pact = Pact.V3("Service.Consumer", "Service.Provider", config);
_pactBuilder = pact.WithHttpInteractions();
_pactBuilder
    .UponReceiving("ItShouldReturnAStreetName_IfAvailable")
        .Given("streets exist")
        .WithRequest(HttpMethod.Post, "/v1.0/street")
        .WithJsonBody(new { Name = "Mocky" })
    .WillRespond()
        .WithStatus(HttpStatusCode.OK)
        .WithHeader("Content-Type", "application/json; charset=utf-8")
        .WithJsonBody(new { Street = "Mock Street" });

await _pactBuilder.VerifyAsync(async ctx =>
{
    var client = new ApiClient(ctx.MockServerUri);
    var response = await client.GetStreetName(new StreetRequest { Name = "Mocky" });
    Assert.AreEqual("Mock Street", response.Street);
});

Pact snippet

"response": {
  "body": {
    "street": "Mock Street"
  },
  "headers": {
    "Content-Type": "application/json; charset=utf-8"
  },
  "status": 200
}

Provider snippet

var pactDir = $"{Directory.GetParent(Directory.GetCurrentDirectory()).Parent.Parent.Parent.Parent.FullName}{Path.DirectorySeparatorChar}pacts";
_pactFile = new FileInfo($"{pactDir}{Path.DirectorySeparatorChar}Service.Consumer-Service.Provider.json");
_pactVerifier = new PactVerifier(new PactVerifierConfig());
_fixture = new WebServerFixture(); // this fixture is the class from the README
_pactVerifier.ServiceProvider("WhenGettingAStreet", _fixture.ServerUri)
    .WithFileSource(_pactFile)
    .WithProviderStateUrl(new Uri(_fixture.ServerUri, "/provider-states"))
    .Verify();
mefellows commented 9 months ago

The consumer runs successfully if I use the CamelCasePropertyNamesContractResolver, and generates a pact with lowercase model names, but the provider verification fails. The provider passes if I use lowercase model names.

I can't make sense of this as it seems to contradict itself. What does the provider need to pass - lower case or upper case? What does the consumer need (the correct answer is "the same as the provider" 😆 )

Nick-Sullivan commented 9 months ago

Both the provider and consumer are expecting upper case, but the pact is generating it as lowercase.

basdijkstra commented 9 months ago

If they're expecting upper case, why are the models you use using camel casing?

Nick-Sullivan commented 9 months ago

Sorry, both are expecting camel case, but its a single word so the only difference is that the first letter is uppercase

basdijkstra commented 9 months ago

Maybe I'm reading your response wrong but if the first letter is / should be uppercase, that's not camel casing.

Can you give an example of the model (or one of it properties) and how it appears in the contract vs. how it is expected by the consumer / should be returned by the provider?

Nick-Sullivan commented 9 months ago

Here is the Consumer model:

public class StreetResponse
{
    public string Street { get; set; }
}

Here is the Provider model:

public class StreetResponse
{
    public string Street { get; set; }
}

Here is what is being saved in the contract:

"response": {
  "body": {
    "street": "Mock Street"
  },
  "headers": {
    "Content-Type": "application/json; charset=utf-8"
  },
  "status": 200
}

Both the consumer and provider have an uppercase 'S', but the contract uses a lowercase 's', which is causing the pact verification to fail

adamrodger commented 9 months ago

Your consumer tests have a camel case property resolver set in the serialisation options, which will lower case the first letter and then upper case each word after that, like firstName

So, if you don't want the contract to publish with a lower case leading character then you'll need to stop telling it to do that by removing that from the serialisation options.

basdijkstra commented 9 months ago

I can confirm this, just tested this in my own project and changing

var config = new PactConfig
{
    PactDir = $"{Directory.GetParent(Directory.GetCurrentDirectory()).Parent.Parent.Parent.Parent.FullName}{Path.DirectorySeparatorChar}pacts",
    LogLevel = PactLogLevel.Debug,
    DefaultJsonSettings = new JsonSerializerSettings
    {
        ContractResolver = new CamelCasePropertyNamesContractResolver(),  // Consumer passes because of this
    }
};

to

var config = new PactConfig
{
    PactDir = $"{Directory.GetParent(Directory.GetCurrentDirectory()).Parent.Parent.Parent.Parent.FullName}{Path.DirectorySeparatorChar}pacts",
    LogLevel = PactLogLevel.Debug,
    DefaultJsonSettings = new JsonSerializerSettings()
};

will generate a contract with Pascal-cased elements / model names.

Nick-Sullivan commented 9 months ago

The CamelCasePropertyNamesContractResolver change has done the trick. Also, the way I was using the HttpClient was making requests become lowercase, which complicated things.

It's working now. Thanks for the help all, I really appreciate it.

mefellows commented 9 months ago

Awesome, thanks for confirming - thanks all for your help. Will close this one off.