Skip to content

Commit 7686959

Browse files
committed
feat!: Make the messaging provider handle scenario requests async
This removes a wrinkle in the API whereby scenario factory methods can be async internally, but because the messaging provider was sync we had to do a sync-over-async call to generate those scenarios. Instead, now the messaging provider runs in an async context, which means the API has changed such that async scenario factory methods are now the default internally. Any sync scenario factories are wrapped such that they become async with `Task.FromResult`. This creates a breaking API change because the verifier and the messaging provider were both previously sync disposable, whereas now they're async disposable so the the messaging prodiver can stop the async request handling context.
1 parent 202ceb3 commit 7686959

File tree

17 files changed

+165
-74
lines changed

17 files changed

+165
-74
lines changed

samples/OrdersApi/Consumer.Tests/Consumer.Tests.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
</PropertyGroup>
66
<ItemGroup>
77
<PackageReference Include="FluentAssertions" Version="6.12.0" />
8-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
8+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
99
<PackageReference Include="Moq" Version="4.20.70" />
10-
<PackageReference Include="xunit" Version="2.6.6" />
11-
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
10+
<PackageReference Include="xunit" Version="2.9.3" />
11+
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
1212
<PrivateAssets>all</PrivateAssets>
1313
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1414
</PackageReference>

samples/OrdersApi/Provider.Tests/Provider.Tests.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
<Project Sdk="Microsoft.NET.Sdk.Web">
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
22
<PropertyGroup>
33
<TargetFramework>net8.0</TargetFramework>
44
<IsPackable>false</IsPackable>
55
</PropertyGroup>
66

77
<ItemGroup>
8-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
9-
<PackageReference Include="xunit" Version="2.6.6" />
10-
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
8+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
9+
<PackageReference Include="xunit" Version="2.9.3" />
10+
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
1111
<PrivateAssets>all</PrivateAssets>
1212
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1313
</PackageReference>

samples/OrdersApi/Provider.Tests/ProviderTests.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Text.Json;
5+
using System.Threading.Tasks;
56
using Microsoft.AspNetCore.Hosting;
67
using Microsoft.Extensions.Hosting;
78
using PactNet;
@@ -14,7 +15,7 @@
1415

1516
namespace Provider.Tests
1617
{
17-
public class ProviderTests : IDisposable
18+
public class ProviderTests : IAsyncLifetime
1819
{
1920
private static readonly Uri ProviderUri = new("http://localhost:5000");
2021

@@ -36,8 +37,6 @@ public ProviderTests(ITestOutputHelper output)
3637
webBuilder.UseStartup<TestStartup>();
3738
})
3839
.Build();
39-
40-
this.server.Start();
4140

4241
this.verifier = new PactVerifier("Orders API", new PactVerifierConfig
4342
{
@@ -49,10 +48,19 @@ public ProviderTests(ITestOutputHelper output)
4948
});
5049
}
5150

52-
public void Dispose()
51+
/// <summary>
52+
/// Called immediately after the class has been created, before it is used.
53+
/// </summary>
54+
public Task InitializeAsync()
55+
{
56+
this.server.Start();
57+
return Task.CompletedTask;
58+
}
59+
60+
public async Task DisposeAsync()
5361
{
62+
await this.verifier.DisposeAsync();
5463
this.server.Dispose();
55-
this.verifier.Dispose();
5664
}
5765

5866
[Fact]

src/PactNet.Abstractions/PactNet.Abstractions.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
<ItemGroup>
1414
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
15+
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
1516
<PackageReference Include="System.Net.Http" Version="4.3.4" />
1617
<PackageReference Include="System.Text.Json" Version="8.0.5" />
1718
</ItemGroup>

src/PactNet.Abstractions/Verifier/Messaging/IMessageScenarios.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Threading.Tasks;
34

45
namespace PactNet.Verifier.Messaging
56
{
@@ -20,6 +21,13 @@ public interface IMessageScenarios
2021
/// <param name="factory">Message content factory</param>
2122
IMessageScenarios Add(string description, Func<dynamic> factory);
2223

24+
/// <summary>
25+
/// Add a message scenario
26+
/// </summary>
27+
/// <param name="description">Scenario description</param>
28+
/// <param name="factory">Message content factory</param>
29+
IMessageScenarios Add(string description, Func<Task<dynamic>> factory);
30+
2331
/// <summary>
2432
/// Add a message scenario by configuring a scenario builder
2533
/// </summary>

src/PactNet.Abstractions/Verifier/Messaging/IMessagingProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace PactNet.Verifier.Messaging
66
/// <summary>
77
/// Messaging provider service, which simulates messaging responses in order to verify interactions
88
/// </summary>
9-
public interface IMessagingProvider : IDisposable
9+
public interface IMessagingProvider : IAsyncDisposable
1010
{
1111
/// <summary>
1212
/// Scenarios configured for the provider

src/PactNet.Abstractions/Verifier/Messaging/Scenario.cs

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Text.Json;
3+
using System.Threading.Tasks;
34

45
namespace PactNet.Verifier.Messaging
56
{
@@ -8,7 +9,7 @@ namespace PactNet.Verifier.Messaging
89
/// </summary>
910
public class Scenario
1011
{
11-
private readonly Func<dynamic> factory;
12+
private readonly Func<Task<dynamic>> factory;
1213

1314
/// <summary>
1415
/// The description of the scenario
@@ -30,7 +31,16 @@ public class Scenario
3031
/// </summary>
3132
/// <param name="description">the scenario description</param>
3233
/// <param name="factory">Message content factory</param>
33-
public Scenario(string description, Func<dynamic> factory)
34+
public Scenario(string description, Func<dynamic> factory) : this(description, Wrap(factory))
35+
{
36+
}
37+
38+
/// <summary>
39+
/// Creates an instance of <see cref="Scenario"/>
40+
/// </summary>
41+
/// <param name="description">the scenario description</param>
42+
/// <param name="factory">Message content factory</param>
43+
public Scenario(string description, Func<Task<dynamic>> factory)
3444
{
3545
this.Description = !string.IsNullOrWhiteSpace(description) ? description : throw new ArgumentException("Description cannot be null or empty");
3646
this.factory = factory ?? throw new ArgumentNullException(nameof(factory));
@@ -44,19 +54,41 @@ public Scenario(string description, Func<dynamic> factory)
4454
/// <param name="metadata">the metadata</param>
4555
/// <param name="settings">Custom JSON serializer settings</param>
4656
public Scenario(string description, Func<dynamic> factory, dynamic metadata, JsonSerializerOptions settings)
57+
: this(description, Wrap(factory))
58+
{
59+
this.Metadata = metadata;
60+
this.JsonSettings = settings;
61+
}
62+
63+
/// <summary>
64+
/// Creates an instance of <see cref="Scenario"/>
65+
/// </summary>
66+
/// <param name="description">the scenario description</param>
67+
/// <param name="factory">Message content factory</param>
68+
/// <param name="metadata">the metadata</param>
69+
/// <param name="settings">Custom JSON serializer settings</param>
70+
public Scenario(string description, Func<Task<dynamic>> factory, dynamic metadata, JsonSerializerOptions settings)
4771
: this(description, factory)
4872
{
4973
this.Metadata = metadata;
5074
this.JsonSettings = settings;
5175
}
5276

5377
/// <summary>
54-
/// Invoke a scenario
78+
/// Invoke a scenario to generate message content
5579
/// </summary>
5680
/// <returns>The scenario message content</returns>
57-
public dynamic Invoke()
81+
public async Task<dynamic> InvokeAsync() => await this.factory();
82+
83+
/// <summary>
84+
/// Wraps a sync factory to be async
85+
/// </summary>
86+
/// <param name="factory">Sync factory</param>
87+
/// <returns>Async factory</returns>
88+
private static Func<Task<dynamic>> Wrap(Func<dynamic> factory) => () =>
5889
{
59-
return this.factory.Invoke();
60-
}
90+
dynamic d = factory();
91+
return Task.FromResult(d);
92+
};
6193
}
6294
}

src/PactNet/Verifier/Messaging/MessageScenarioBuilder.cs

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ internal class MessageScenarioBuilder : IMessageScenarioBuilder
1111
{
1212
private readonly string description;
1313

14-
private Func<dynamic> factory;
14+
private Func<Task<dynamic>> factory;
1515
private dynamic metadata = new { ContentType = "application/json" };
1616
private JsonSerializerOptions settings;
1717

@@ -36,23 +36,37 @@ public IMessageScenarioBuilder WithMetadata(dynamic metadata)
3636
}
3737

3838
/// <summary>
39-
/// Set the action of the scenario
39+
/// Set the content factory of the scenario. The factory is invoked each time the scenario is required.
4040
/// </summary>
4141
/// <param name="factory">Content factory</param>
4242
public void WithContent(Func<dynamic> factory)
4343
{
44-
this.factory = factory ?? throw new ArgumentNullException(nameof(factory));
44+
if (factory == null)
45+
{
46+
throw new ArgumentNullException(nameof(factory));
47+
}
48+
49+
this.WithAsyncContent(() => Task.FromResult<dynamic>(factory()));
4550
}
4651

4752
/// <summary>
48-
/// Set the content of the scenario
53+
/// Set the content factory of the scenario. The factory is invoked each time the scenario is required.
4954
/// </summary>
5055
/// <param name="factory">Content factory</param>
5156
/// <param name="settings">Custom JSON serializer settings</param>
5257
public void WithContent(Func<dynamic> factory, JsonSerializerOptions settings)
5358
{
54-
this.factory = factory ?? throw new ArgumentNullException(nameof(factory));
55-
this.settings = settings ?? throw new ArgumentNullException(nameof(settings));
59+
if (factory == null)
60+
{
61+
throw new ArgumentNullException(nameof(factory));
62+
}
63+
64+
if (settings == null)
65+
{
66+
throw new ArgumentNullException(nameof(settings));
67+
}
68+
69+
this.WithAsyncContent(() => Task.FromResult<dynamic>(factory()), settings);
5670
}
5771

5872
/// <summary>
@@ -61,12 +75,7 @@ public void WithContent(Func<dynamic> factory, JsonSerializerOptions settings)
6175
/// <param name="factory">Content factory</param>
6276
public void WithAsyncContent(Func<Task<dynamic>> factory)
6377
{
64-
if (factory == null)
65-
{
66-
throw new ArgumentNullException(nameof(factory));
67-
}
68-
69-
this.WithContent(() => factory().GetAwaiter().GetResult());
78+
this.factory = factory ?? throw new ArgumentNullException(nameof(factory));
7079
}
7180

7281
/// <summary>
@@ -76,12 +85,8 @@ public void WithAsyncContent(Func<Task<dynamic>> factory)
7685
/// <param name="settings">Custom JSON serializer settings</param>
7786
public void WithAsyncContent(Func<Task<dynamic>> factory, JsonSerializerOptions settings)
7887
{
79-
if (factory == null)
80-
{
81-
throw new ArgumentNullException(nameof(factory));
82-
}
83-
84-
this.WithContent(() => factory().GetAwaiter().GetResult(), settings);
88+
this.factory = factory ?? throw new ArgumentNullException(nameof(factory));
89+
this.settings = settings ?? throw new ArgumentNullException(nameof(settings));
8590
}
8691

8792
/// <summary>

src/PactNet/Verifier/Messaging/MessageScenarios.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Collections.ObjectModel;
4+
using System.Threading.Tasks;
45

56
namespace PactNet.Verifier.Messaging
67
{
@@ -35,6 +36,18 @@ public MessageScenarios()
3536
/// <param name="description">Scenario description</param>
3637
/// <param name="factory">Message content factory</param>
3738
public IMessageScenarios Add(string description, Func<dynamic> factory)
39+
{
40+
Func<Task<dynamic>> asyncFactory = () => Task.FromResult<dynamic>(factory());
41+
42+
return this.Add(description, asyncFactory);
43+
}
44+
45+
/// <summary>
46+
/// Add a message scenario
47+
/// </summary>
48+
/// <param name="description">Scenario description</param>
49+
/// <param name="factory">Message content factory</param>
50+
public IMessageScenarios Add(string description, Func<Task<dynamic>> factory)
3851
{
3952
var scenario = new Scenario(description, factory, JsonMetadata, null);
4053
this.scenarios.Add(description, scenario);

0 commit comments

Comments
 (0)