Skip to content

C# test suite PoC #50

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,12 @@ node_modules

# Mongosh temporary files
temp

# C# Debug files
Debug/

# NuGet generated artifact files
obj/

# User-specific settings files from JetBrains Rider and ReSharper
*.DotSettings.user
132 changes: 132 additions & 0 deletions usage-examples/csharp/driver/Examples/Aggregation/GroupTotal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// :replace-start: {
// "terms": {
// "_order": "order",
// "_aggDb": "aggDb"
// }
// }

namespace Examples.Aggregation;

using MongoDB.Driver;
using MongoDB.Bson;

public class GroupTotal
{
private IMongoDatabase? _aggDB;
private IMongoCollection<Order>? _orders;

public void LoadSampleData()
{
var uri = DotNetEnv.Env.GetString("CONNECTION_STRING", "Env variable not found. Verify you have a .env file with a valid connection string.");
// :snippet-start: connection-string
// :uncomment-start:
//var uri = "mongodb+srv://mongodb-example:27017";
// :uncomment-end:
// :snippet-end:
var client = new MongoClient(uri);
_aggDB = client.GetDatabase("agg_tutorials_db");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idiomatic C# doesn't use _variable except in private getters/setters. Also, it's recommended that variables names are more explicit. In this case, I'd go with sampleDatabase or aggregationDatabase or similar.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as all cases below this, too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, the DB name comes from an existing code example. This is the DB name used across 5 pages across all of the programming languages, so changing it would be non-trivial and out of scope of this PR.

// :snippet-start: add-sample-data
_orders = _aggDB.GetCollection<Order>("orders");
_orders.DeleteMany(Builders<Order>.Filter.Empty);

_orders.InsertMany(new List<Order>
{
new Order
{
CustomerId = "[email protected]",
OrderDate = DateTime.Parse("2020-05-30T08:35:52Z"),
Value = 231
},
new Order
{
CustomerId = "[email protected]",
OrderDate = DateTime.Parse("2020-01-13T09:32:07Z"),
Value = 99
},
new Order
{
CustomerId = "[email protected]",
OrderDate = DateTime.Parse("2020-01-01T08:25:37Z"),
Value = 63
},
new Order
{
CustomerId = "[email protected]",
OrderDate = DateTime.Parse("2019-05-28T19:13:32Z"),
Value = 2
},
new Order
{
CustomerId = "[email protected]",
OrderDate = DateTime.Parse("2020-11-23T22:56:53Z"),
Value = 187
},
new Order
{
CustomerId = "[email protected]",
OrderDate = DateTime.Parse("2020-08-18T23:04:48Z"),
Value = 4
},
new Order
{
CustomerId = "[email protected]",
OrderDate = DateTime.Parse("2020-12-26T08:55:46Z"),
Value = 4
},
new Order
{
CustomerId = "[email protected]",
OrderDate = DateTime.Parse("2021-02-28T07:49:32Z"),
Value = 1024
},
new Order
{
CustomerId = "[email protected]",
OrderDate = DateTime.Parse("2020-10-03T13:49:44Z"),
Value = 102
}
});
// :snippet-end:
}

public List<BsonDocument> PerformAggregation()
{
if (_aggDB == null || _orders == null)
{
throw new InvalidOperationException("You must call LoadSampleData before performing aggregation.");
}

// :snippet-start: match
var results = _orders.Aggregate()
.Match(o => o.OrderDate >= DateTime.Parse("2020-01-01T00:00:00Z") && o.OrderDate < DateTime.Parse("2021-01-01T00:00:00Z"))
// :snippet-end:
// :snippet-start: sort-order-date
.SortBy(o => o.OrderDate)
// :snippet-end:
// :snippet-start: group
.Group(
o => o.CustomerId,
g => new
{
CustomerId = g.Key,
FirstPurchaseDate = g.First().OrderDate,
TotalValue = g.Sum(i => i.Value),
TotalOrders = g.Count(),
Orders = g.Select(i => new { i.OrderDate, i.Value }).ToList()
}
)
// :snippet-end:
// :snippet-start: sort-first-order
.SortBy(c => c.FirstPurchaseDate)
.As<BsonDocument>();
// :snippet-end:

foreach (var result in results.ToList())
{
Console.WriteLine(result);
}

return results.ToList();
}
}
// :replace-end:
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{ "CustomerId" : "[email protected]", "FirstPurchaseDate" : { "$date" : "2020-01-01T08:25:37Z" }, "TotalValue" : 63, "TotalOrders" : 1, "Orders" : [{ "OrderDate" : { "$date" : "2020-01-01T08:25:37Z" }, "Value" : 63 }] }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how this is used...should it be pretty-formatted?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just pulling it in from the docs page where it's currently used. We don't pretty format it there, so I kept the existing formatting. (Presumably because pretty formatting it would make it much longer, and that would take up a lot of real estate on the page, but I'm making an assumption.)

{ "CustomerId" : "[email protected]", "FirstPurchaseDate" : { "$date" : "2020-01-13T09:32:07Z" }, "TotalValue" : 436, "TotalOrders" : 4, "Orders" : [{ "OrderDate" : { "$date" : "2020-01-13T09:32:07Z" }, "Value" : 99 }, { "OrderDate" : { "$date" : "2020-05-30T08:35:52Z" }, "Value" : 231 }, { "OrderDate" : { "$date" : "2020-10-03T13:49:44Z" }, "Value" : 102 }, { "OrderDate" : { "$date" : "2020-12-26T08:55:46Z" }, "Value" : 4 }] }
{ "CustomerId" : "[email protected]", "FirstPurchaseDate" : { "$date" : "2020-08-18T23:04:48Z" }, "TotalValue" : 191, "TotalOrders" : 2, "Orders" : [{ "OrderDate" : { "$date" : "2020-08-18T23:04:48Z" }, "Value" : 4 }, { "OrderDate" : { "$date" : "2020-11-23T22:56:53Z" }, "Value" : 187 }] }
15 changes: 15 additions & 0 deletions usage-examples/csharp/driver/Examples/Aggregation/Order.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;

namespace Examples.Aggregation;

// :snippet-start: class-declaration
public class Order
{
[BsonId]
public ObjectId Id { get; set; }
public string CustomerId { get; set; }
public DateTime OrderDate { get; set; }
public int Value { get; set; }
}
// :snippet-end:
15 changes: 15 additions & 0 deletions usage-examples/csharp/driver/Examples/Examples.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="DotNetEnv" Version="3.1.1" />
<PackageReference Include="MongoDB.Driver" Version="3.4.0" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions usage-examples/csharp/driver/Examples/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Console.WriteLine("This project is not intended to be run as a console application. Run the tests using the 'Tests' solution.");
105 changes: 105 additions & 0 deletions usage-examples/csharp/driver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# C# Driver Code Example Test PoC

This is a PoC to explore testing C# Driver code examples for MongoDB documentation.

The structure of this C# project is as follows:

- `driver.sln`: This solution contains the following projects:
- `Examples`: This project contains example code and output to validate.
- `Tests`: This project contains the test infrastructure to actually run
the tests by invoking the example code.

## To run the tests locally

### Create a MongoDB Docker Deployment
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? I've never used a Docker instance to test C# projects.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a holdover from the other test suites - not specific to C#. But my recommendation is for us to test things locally unless Atlas is required, as local testing doesn't require spinning up cloud infrastructure.


To run these tests locally, you need a MongoDB Docker deployment. Make sure Docker is
running, and then:

1. Pull the MongoDB image from Docker Hub:

```shell
docker pull mongo
```
2. Run the container:

```shell
docker run --name mongodb-test -d -p 27017:27017 mongo
```

3. Verify the container is running:

```shell
docker ps
```

The output resembles:

```text
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ef70cce38f26 mongo "/usr/local/bin/runn…" 29 hours ago Up 29 hours (healthy) 127.0.0.1:63201->27017/tcp mongodb-test
```

You may note the actual port is different than `27017`, if something else is already running on
`27017` on your machine. Note the port next to the IP address for running the tests. Alternately, you can get just
the port info for your container using the following command:

```shell
docker port mongodb-test
```

The output resembles:

```text
27017/tcp -> 0.0.0.0:27017
27017/tcp -> [::]:27017
```

### Create a .env file
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on my initial comment about using the 3-party library, this may change.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good point. I've changed the way I'm loading this so it's now being used properly to load .env vars at the beginning of the test suite run instead of having to read the file over and over again in various places to get the vars. I think the third-party lib is justifiable for env var management, and according to NuGet, this one has 12 million users. I think it's probably safe in this case to assume it's reasonably maintained.


Create a file named `.env` at the root of the `/driver` directory.
Add the following values to your .env file, similar to the following example:

```
CONNECTION_STRING="mongodb://localhost:27017"
SOLUTION_ROOT="/Users/dachary.carey/workspace/docs-code-examples/usage-examples/csharp/driver/"
```

- `CONNECTION_STRING`: replace the port with the port where your local deployment is running.
- `SOLUTION_ROOT`: insert the path to the `driver` directory on your filesystem.

### Install the dependencies

This test suite requires you to have `.NET` v9.0 installed. If you
do not yet have .NET installed, refer to
[the .NET installation page](https://learn.microsoft.com/en-us/dotnet/core/install/macos)
for details.

From the root of each project directory, run the following command to install
dependencies:

```
dotnet restore
```

### Run the tests

You can run tests from the command line or through your IDE.

#### Run All Tests from the command line

From the `/drivers` directory, run:

```
dotnet test
```

#### Run Individual Tests from the command line

You can run a single test within a given test suite (file).

From the `/drivers` directory, run:

```
dotnet test --filter "FullyQualifiedName=YourNamespace.YourTestClass.YourTestMethod"
```
31 changes: 31 additions & 0 deletions usage-examples/csharp/driver/Tests/GroupTotalTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Examples.Aggregation;

namespace Tests;

public class GroupTotalTest
{
private GroupTotal _example;
[SetUp]
public void Setup()
{
_example = new GroupTotal();
_example.LoadSampleData();
}

[Test]
public void TestOutputMatchesDocs()
{
var results = _example.PerformAggregation();

var solutionRoot = DotNetEnv.Env.GetString("SOLUTION_ROOT", "Env variable not found. Verify you have a .env file with a valid connection string.");
var outputLocation = "Examples/Aggregation/GroupTotalOutput.txt";
var fullPath = Path.Combine(solutionRoot, outputLocation);
var fileData = TestUtils.ReadBsonDocumentsFromFile(fullPath);

Assert.That(results.Count, Is.EqualTo(fileData.Length), $"Result count {results.Count} does not match output example length {fileData.Length}.");
for (var i = 0; i < fileData.Length; i++)
{
Assert.That(fileData[i], Is.EqualTo(results[i]), $"Mismatch at index {i}: expected {fileData[i]}, got {results[i]}.");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooo you did it here. Nice!

}
}
}
13 changes: 13 additions & 0 deletions usage-examples/csharp/driver/Tests/TestSuiteSetup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Tests;

using DotNetEnv;

[SetUpFixture]
public class TestSuiteSetup
{
[OneTimeSetUp]
public void GlobalSetup()
{
Env.TraversePath().Load();
}
}
20 changes: 20 additions & 0 deletions usage-examples/csharp/driver/Tests/TestUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using MongoDB.Bson;

public static class TestUtils
{
public static BsonDocument[] ReadBsonDocumentsFromFile(string filePath)
{
var bsonDocuments = new List<BsonDocument>();

foreach (var line in File.ReadLines(filePath))
{
if (!string.IsNullOrWhiteSpace(line))
{
var bsonDoc = BsonDocument.Parse(line);
bsonDocuments.Add(bsonDoc);
}
}

return bsonDocuments.ToArray();
}
}
27 changes: 27 additions & 0 deletions usage-examples/csharp/driver/Tests/Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1"/>
<PackageReference Include="NUnit" Version="4.2.2"/>
<PackageReference Include="NUnit.Analyzers" Version="4.3.0"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0"/>
</ItemGroup>

<ItemGroup>
<Using Include="NUnit.Framework"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Examples\Examples.csproj" />
</ItemGroup>

</Project>
Loading