Skip to content

Commit 73c1124

Browse files
authored
Add example for usage of PACT for event based system (#1)
Add support for events and related contract tests
1 parent e46a4e2 commit 73c1124

35 files changed

Lines changed: 666 additions & 52 deletions

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ bin/
22
obj/
33
/packages/
44
riderModule.iml
5-
/_ReSharper.Caches/
5+
/_ReSharper.Caches/
6+
.idea

MessageBroker/EventPublisher.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System.Text;
2+
using Newtonsoft.Json;
3+
using RabbitMQ.Client;
4+
5+
namespace MessageBroker;
6+
7+
public class EventPublisher: IEventPublisher
8+
{
9+
private readonly IModel _channel;
10+
public EventPublisher()
11+
{
12+
var factory = new ConnectionFactory { HostName = "localhost"};
13+
var connection = factory.CreateConnection();
14+
_channel = connection.CreateModel();
15+
}
16+
17+
public async Task PublishAsync<T>(T message, string queueName)
18+
{
19+
_channel.QueueDeclare(queue: queueName,
20+
durable: false,
21+
exclusive: false,
22+
autoDelete: false,
23+
arguments: null);
24+
25+
var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message));
26+
27+
_channel.BasicPublish(exchange: string.Empty,
28+
routingKey: queueName,
29+
basicProperties: null,
30+
body: body);
31+
await Task.CompletedTask;
32+
}
33+
}
34+
35+
public class FakeEventPublisher: IEventPublisher
36+
{
37+
public async Task PublishAsync<T>(T message, string queueName)
38+
{
39+
Console.WriteLine($"Event published on {queueName}");
40+
await Task.CompletedTask;
41+
}
42+
}

MessageBroker/IEventPublisher.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace MessageBroker;
2+
3+
public interface IEventPublisher
4+
{
5+
Task PublishAsync<T>(T message, string queueName);
6+
}

MessageBroker/IListener.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace MessageBroker;
2+
3+
public interface IListener
4+
{
5+
void StartListening();
6+
}

MessageBroker/MessageBroker.csproj

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
11+
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
12+
</ItemGroup>
13+
14+
</Project>

PactNet.ConsumerOne.UnitTest/PactNet.ConsumerOne.UnitTest.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
</PropertyGroup>
1010

1111
<ItemGroup>
12+
<PackageReference Include="FluentAssertions" Version="6.12.0" />
1213
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
1314
<PackageReference Include="Moq" Version="4.20.70" />
1415
<PackageReference Include="NUnit" Version="4.1.0" />
@@ -27,6 +28,7 @@
2728

2829
<ItemGroup>
2930
<ProjectReference Include="..\PactNet.ConsumerOne\PactNet.ConsumerOne.csproj" />
31+
<ProjectReference Include="..\PactNet.Provider.UnitTest\PactNet.Provider.UnitTest.csproj" />
3032
</ItemGroup>
3133

3234
</Project>

PactNet.ConsumerOne.UnitTest/ReportCardControllerTest.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ namespace PactNet.ConsumerOne.UnitTest
99
public class ReportCardControllerTest
1010
{
1111
private readonly IPactBuilderV3 _pactBuilder;
12-
12+
private const string ValidStudentId = "067a50c5-0b23-485e-b018-17c66b2422ff";
13+
1314
public ReportCardControllerTest()
1415
{
1516
var pactDir =
@@ -33,8 +34,8 @@ public async Task Get_Student_When_The_Student_With_Id_1_Exists()
3334
// Arrange
3435
_pactBuilder
3536
.UponReceiving("A GET request to retrieve the student")
36-
.Given("There is student with id 1")
37-
.WithRequest(HttpMethod.Get, "/students/" + 1)
37+
.Given($"There is student with id {ValidStudentId}")
38+
.WithRequest(HttpMethod.Get, $"/students/{ValidStudentId}")
3839
.WithHeader("Accept", "application/json")
3940
.WillRespond()
4041
.WithStatus(HttpStatusCode.OK)
@@ -44,17 +45,17 @@ public async Task Get_Student_When_The_Student_With_Id_1_Exists()
4445
firstName = "Raju",
4546
lastName = "Rastogi",
4647
address = "Delhi",
47-
id = 1
48+
id = ValidStudentId
4849
});
4950

5051
await _pactBuilder.VerifyAsync(async ctx =>
5152
{
5253
// Act
5354
var client = new StudentClient(ctx.MockServerUri);
54-
var studentDto = await client.GetStudentById(1);
55+
var studentDto = await client.GetStudentById(ValidStudentId);
5556

5657
// Assert
57-
Assert.That(studentDto.Id, Is.EqualTo(1));
58+
Assert.That(studentDto.Id, Is.EqualTo(ValidStudentId));
5859
});
5960
}
6061

@@ -65,7 +66,7 @@ public void Get_Student_When_The_StudentId_Is_Invalid()
6566
_pactBuilder
6667
.UponReceiving("A GET request to retrieve the student with invalid student id")
6768
.Given("There is student is at least one valid student present")
68-
.WithRequest(HttpMethod.Get, "/students/" + -999)
69+
.WithRequest(HttpMethod.Get, "/students/some-invalid-id")
6970
.WithHeader("Accept", "application/json")
7071
.WillRespond()
7172
.WithStatus(HttpStatusCode.NoContent);
@@ -74,7 +75,7 @@ public void Get_Student_When_The_StudentId_Is_Invalid()
7475
{
7576
// Act
7677
var client = new StudentClient(ctx.MockServerUri);
77-
Assert.ThrowsAsync<Exception>(async () => await client.GetStudentById(-999));
78+
Assert.ThrowsAsync<Exception>(async () => await client.GetStudentById("some-invalid-id"));
7879
});
7980
}
8081
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using Newtonsoft.Json;
2+
using Newtonsoft.Json.Serialization;
3+
using PactNet.ConsumerOne.Models.Events;
4+
using PactNet.Infrastructure.Outputters;
5+
using PactNet.Matchers;
6+
using PactNet.Provider.UnitTest;
7+
using Xunit;
8+
using Xunit.Abstractions;
9+
using FluentAssertions;
10+
11+
namespace PactNet.ConsumerOne.UnitTest;
12+
13+
public class StudentConsumerEventTests
14+
{
15+
private const string UuidRegex = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}";
16+
private readonly IMessagePactBuilderV3 _messagePact;
17+
private ITestOutputHelper OutputHelper { get; }
18+
19+
public StudentConsumerEventTests(ITestOutputHelper output)
20+
{
21+
OutputHelper = output;
22+
23+
IPactV3 v3 = Pact.V3("Result", "Student Event", new PactConfig
24+
{
25+
PactDir = "../../../../Pacts",
26+
DefaultJsonSettings = new JsonSerializerSettings
27+
{
28+
ContractResolver = new CamelCasePropertyNamesContractResolver()
29+
},
30+
Outputters = new List<IOutput>
31+
{
32+
new XunitOutput(OutputHelper)
33+
},
34+
35+
LogLevel = PactLogLevel.Information
36+
});
37+
38+
_messagePact = v3.WithMessageInteractions();
39+
}
40+
41+
[Fact]
42+
public void ReceivesExhibitorCreatedEvent()
43+
{
44+
_messagePact
45+
.ExpectsToReceive("a StudentCreatedEvent")
46+
.WithJsonContent(new
47+
{
48+
StudentId = Match.Regex("2274a1f8-7d93-11ee-b962-0242ac120002",$"^{UuidRegex}$"),
49+
StandardId = Match.Integer(2),
50+
FirstName = Match.Regex("FirstName",$"^[a-zA-Z]{{3,25}}$$"),
51+
LastName = Match.Regex("LastName",$"^[a-zA-Z]{{3,25}}$$")
52+
}
53+
)
54+
.Verify<StudentCreatedEvent>(message =>
55+
{
56+
message.StudentId.Should().BeEquivalentTo("2274a1f8-7d93-11ee-b962-0242ac120002");
57+
message.FirstName.Should().BeEquivalentTo("FirstName");
58+
message.LastName.Should().BeEquivalentTo("LastName");
59+
message.StandardId.Should().Be(2);
60+
}
61+
);
62+
}
63+
}

PactNet.ConsumerOne/Controllers/ReportCardController.cs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Net.Http;
52
using System.Threading.Tasks;
63
using Microsoft.AspNetCore.Mvc;
74
using PactNet.ConsumerOne.HttpClients;
@@ -22,18 +19,43 @@ public ReportCardController(IStudentClient studentClient)
2219

2320
[HttpGet]
2421
[Route("{id}")]
25-
public async Task<IActionResult> GetById(int id)
22+
public async Task<IActionResult> GetById(string id)
2623
{
2724
var student = await _studentClient.GetStudentById(id);
2825

2926
var report = new ReportCard()
3027
{
31-
Id = id,
28+
Id = Random.Shared.Next(),
3229
Student = student,
33-
Score = id * 100
30+
Score = 2 * 100
3431
};
3532

3633
return Ok(report);
3734
}
35+
36+
[HttpPost]
37+
public async Task<IActionResult> Post(string id)
38+
{
39+
try
40+
{
41+
var student = await _studentClient.GetStudentById(id);
42+
if (student == null)
43+
{
44+
return NotFound();
45+
}
46+
var reportCard = new ReportCard()
47+
{
48+
Id = DateTime.Now.Year + Random.Shared.Next(),
49+
Student = student,
50+
Score = 9.5d
51+
};
52+
return Ok(reportCard);
53+
}
54+
catch (Exception e)
55+
{
56+
Console.WriteLine(e);
57+
return StatusCode(500);
58+
}
59+
}
3860
}
3961
}

PactNet.ConsumerOne/HttpClients/StudentClient.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace PactNet.ConsumerOne.HttpClients
1010
{
1111
public interface IStudentClient
1212
{
13-
Task<StudentDto> GetStudentById(int studentId);
13+
Task<StudentDto> GetStudentById(string studentId);
1414
}
1515

1616
public class StudentClient: IStudentClient
@@ -22,7 +22,7 @@ public StudentClient(Uri baseUri = null)
2222
this._client = new HttpClient { BaseAddress = baseUri ?? new Uri("https://localhost:5001/") };
2323
}
2424

25-
public async Task<StudentDto> GetStudentById(int studentId)
25+
public async Task<StudentDto> GetStudentById(string studentId)
2626
{
2727
var request = new HttpRequestMessage(HttpMethod.Get, "/students/" + studentId);
2828
request.Headers.Add("Accept", "application/json");
@@ -43,6 +43,11 @@ public async Task<StudentDto> GetStudentById(int studentId)
4343
? JsonConvert.DeserializeObject<StudentDto>(content)
4444
: null;
4545
}
46+
47+
if (status == HttpStatusCode.NotFound)
48+
{
49+
return null;
50+
}
4651

4752
throw new Exception(reasonPhrase);
4853
}

0 commit comments

Comments
 (0)