Skip to content

Commit

Permalink
#15 - Complete (#20)
Browse files Browse the repository at this point in the history
* #15 - Complete

* Add .Net 5 to pipeline setups
  • Loading branch information
MelGrubb committed Aug 9, 2022
1 parent ebdc130 commit 9ceeaf5
Show file tree
Hide file tree
Showing 26 changed files with 636 additions and 206 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ jobs:
with:
dotnet-version: 3.1.x

- name: Setup .NET 5
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x

- name: Setup .NET 6
uses: actions/setup-dotnet@v1
with:
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ jobs:
with:
dotnet-version: 3.1.x

- name: Setup .NET 5
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x

- name: Setup .NET 6
uses: actions/setup-dotnet@v1
with:
Expand Down
4 changes: 4 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ I know that the test cares about the User's first name because it mentioned it.

One feature of the generated builders is that the various "With" methods come in multiple flavors. There is the typical version that takes a simple value, such as the Guid Id value used in this example. In addition, there are overloads that take lambda expressions to be evaluated when the builder's Build method is finally invoked at runtime. This can be useful as a way to delay the execution of potentially expensive code until the last moment in case the value is changed later on. Each "With" method can be called multiple times, with subsequent values overwriting previous ones. In other words, "Last in wins". Builders represent a *plan* for creating an object rather than the object itself, and plans are always subject to change.

## Manipulating internal properties

Your objects may have internal properties that you expose to unit tests via the ```InternalsVisibleTo``` attribute. In order to manipulate these properties from the builders, you must explicitly tell the builder to expose them. In the ```BuilderFor``` attribute, add a second parameter with the value ```true``` or a named parameter called "includeInternals". This will tell that specific builder to include internal properties in addition to the public properties as it does now.

### Customizing the code templates

Custom code templates are currently on hold due to changes in the source generator mechanism that came with .net 6. The mechanism used by the previous ISourceGenerator interface is no longer present in the IIncrementalGenerator interface. If there is sufficient interest, and if the modern equivalent can be identified, I may revisit this in the future. It was only an experiment before, and easy enough to reproduce if needed. Otherwise, pull requests for new features and changes are welcome.
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,12 @@
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>BuilderGenerator.Test.Net50</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>BuilderGenerator.Test.Net60</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
using System;

namespace BuilderGenerator.Test.Core.Models.Entities
namespace BuilderGenerator.Test.Core.Models.Entities;

public abstract class AuditableEntity : Entity
{
public abstract class AuditableEntity : Entity
{
public DateTime CreatedAt { get; set; }
public string CreatedBy { get; set; }
public DateTime UpdatedAt { get; set; }
public string UpdatedBy { get; set; }
}
public DateTime CreatedAt { get; set; }
public string CreatedBy { get; set; }
public DateTime UpdatedAt { get; set; }
public string UpdatedBy { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
using System;
using System.Collections.Generic;

namespace BuilderGenerator.Test.Core.Models.Entities
namespace BuilderGenerator.Test.Core.Models.Entities;

public class CollectionTypesSample
{
public class CollectionTypesSample
{
public string[] ReadOnlyArray { get; } = Array.Empty<string>();
public ICollection<string> ReadOnlyCollection { get; } = new List<string>();
public IEnumerable<string> ReadOnlyEnumerable { get; } = new List<string>();
public HashSet<string> ReadOnlyHashSet { get; } = new HashSet<string>();
public List<string> ReadOnlyList { get; } = new List<string>();
public string[] ReadOnlyArray { get; } = Array.Empty<string>();
public ICollection<string> ReadOnlyCollection { get; } = new List<string>();
public IEnumerable<string> ReadOnlyEnumerable { get; } = new List<string>();
public HashSet<string> ReadOnlyHashSet { get; } = new();
public List<string> ReadOnlyList { get; } = new();

public string[] ReadWriteArray { get; set; } = Array.Empty<string>();
public string[] ReadWriteArray { get; set; } = Array.Empty<string>();

public ICollection<string> ReadWriteCollection { get; set; } = new List<string>();
public ICollection<string> ReadWriteCollection { get; set; } = new List<string>();

public IEnumerable<string> ReadWriteEnumerable { get; set; } = new List<string>();
public IEnumerable<string> ReadWriteEnumerable { get; set; } = new List<string>();

public HashSet<string> ReadWriteHashSet { get; set; } = new HashSet<string>();
public HashSet<string> ReadWriteHashSet { get; set; } = new();

public List<string> ReadWriteList { get; set; } = new List<string>();
}
public List<string> ReadWriteList { get; set; } = new();
}
12 changes: 7 additions & 5 deletions src/BuilderGenerator.Test.Core/Models/Entities/Entity.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;

namespace BuilderGenerator.Test.Core.Models.Entities
namespace BuilderGenerator.Test.Core.Models.Entities;

public abstract class Entity
{
public abstract class Entity
{
public Guid Id { get; set; }
}
public Guid Id { get; set; }

// The Builders should only expose this in builder classes where the attribute has enabled it
internal string InternalString { get; set; }
}
14 changes: 7 additions & 7 deletions src/BuilderGenerator.Test.Core/Models/Entities/Order.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
using System.Collections.Generic;
using BuilderGenerator.Test.Core.Models.Enums;

namespace BuilderGenerator.Test.Core.Models.Entities
namespace BuilderGenerator.Test.Core.Models.Entities;

public class Order : AuditableEntity
{
public class Order : AuditableEntity
{
public DateTime OrderDate { get; set; }
public ICollection<OrderItem> Orders { get; set; } = new List<OrderItem>();
public OrderStatus Status { get; set; }
}
public ICollection<OrderItem> Items { get; set; } = new List<OrderItem>();
public DateTime OrderDate { get; set; }
public ICollection<OrderItem> Orders { get; set; } = new List<OrderItem>();
public OrderStatus Status { get; set; }
}
13 changes: 6 additions & 7 deletions src/BuilderGenerator.Test.Core/Models/Entities/OrderItem.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
using System;

namespace BuilderGenerator.Test.Core.Models.Entities
namespace BuilderGenerator.Test.Core.Models.Entities;

public class OrderItem : AuditableEntity
{
public class OrderItem : AuditableEntity
{
public Guid ItemId { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
public Guid ItemId { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
15 changes: 7 additions & 8 deletions src/BuilderGenerator.Test.Core/Models/Entities/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@

using System.Collections.Generic;

namespace BuilderGenerator.Test.Core.Models.Entities
namespace BuilderGenerator.Test.Core.Models.Entities;

public class User : AuditableEntity
{
public class User : AuditableEntity
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? MiddleName { get; set; }
public ICollection<Order> Orders { get; set; } = new List<Order>();
}
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? MiddleName { get; set; }
public ICollection<Order> Orders { get; set; } = new List<Order>();
}
18 changes: 18 additions & 0 deletions src/BuilderGenerator.Test.Net50/BuilderGenerator.Test.Net50.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\BuilderGenerator.Test.Core\BuilderGenerator.Test.Core.csproj" />
<ProjectReference Include="..\BuilderGenerator\BuilderGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Shouldly" Version="4.0.3" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using BuilderGenerator.Test.Core.Models.Entities;

namespace BuilderGenerator.Test.Net50.Builders;

[BuilderFor(typeof(CollectionTypesSample))]
public partial class CollectionTypeSampleBuilder
{
}
29 changes: 29 additions & 0 deletions src/BuilderGenerator.Test.Net50/Builders/OrderBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using BuilderGenerator.Test.Core.Models.Entities;

namespace BuilderGenerator.Test.Net50.Builders;

[BuilderFor(typeof(Order), true)]
public partial class OrderBuilder
{
public static OrderBuilder Simple()
{
var builder = new OrderBuilder()
.WithId(Guid.NewGuid);

return builder;
}

public static OrderBuilder Typical()
{
var builder = Simple()
.WithItems(
() => new List<OrderItem>
{
OrderItemBuilder.Simple().Build(),
});

return builder;
}
}
16 changes: 16 additions & 0 deletions src/BuilderGenerator.Test.Net50/Builders/OrderItemBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using BuilderGenerator.Test.Core.Models.Entities;

namespace BuilderGenerator.Test.Net50.Builders;

[BuilderFor(typeof(OrderItem))]
public partial class OrderItemBuilder
{
public static OrderItemBuilder Simple()
{
var builder = new OrderItemBuilder()
.WithId(Guid.NewGuid);

return builder;
}
}
15 changes: 15 additions & 0 deletions src/BuilderGenerator.Test.Net50/Builders/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Partial Builders #

This folder contains the hand-written half of any builders. Create static factory methods for each well-known or named object instance you want to create.

## Suggestions ##

In addition to specific factory methods, you should define some general-purpose methods such as:

### Simple/Minimal ###

Creates the simplest, most minimal instance that will pass validation. Only fill in the required fields, and leave everything else at their default values. This can serve as the starting point for other, more specific factory methods. For instance, a minimal Customer may be missing address information, and would have no orders.

### Typical ###

This method should return a "typical" example of the class. For a Customer entity, this might mean that the shipping and billing addresses are filled in, and the Customer has multiple orders in various states.
32 changes: 32 additions & 0 deletions src/BuilderGenerator.Test.Net50/Builders/UserBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using BuilderGenerator.Test.Core.Models.Entities;

namespace BuilderGenerator.Test.Net50.Builders;

[BuilderFor(typeof(User))]
public partial class UserBuilder
{
public static UserBuilder Simple()
{
var builder = new UserBuilder()
.WithId(Guid.NewGuid)
.WithFirstName(() => Guid.NewGuid().ToString())
.WithMiddleName(() => Guid.NewGuid().ToString())
.WithLastName(() => Guid.NewGuid().ToString());

return builder;
}

public static UserBuilder Typical()
{
var builder = Simple()
.WithOrders(
() => new List<Order>
{
OrderBuilder.Simple().Build(),
});

return builder;
}
}
5 changes: 5 additions & 0 deletions src/BuilderGenerator.Test.Net50/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# BuilderGenerator.Test.Net60 #

This project provides tests the BuilderGenerator package being used in a .Net 6.0 project using a simple series of inter-related classes defined in the BuiderGenerator.Test.Core project.

The reference to the BuilderGenerator is a direct project reference, so it can be used to rapidly check the results of changes, but does not represent the real-world usage of the generator. It tests the functionality of the builder, but not the deployment mechanism.
66 changes: 66 additions & 0 deletions src/BuilderGenerator.Test.Net50/Tests/OrderBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using BuilderGenerator.Test.Core.Models.Entities;
using BuilderGenerator.Test.Core.Models.Enums;
using BuilderGenerator.Test.Net50.Builders;
using NUnit.Framework;
using Shouldly;

namespace BuilderGenerator.Test.Net50.Tests;

[TestFixture]
public class OrderBuilderTests
{
private Guid _id;
private string _internalString;
private DateTime _orderDate;
private Order _result;
private OrderStatus _status;

[Test]
public void OrderBuilder_can_set_properties()
{
_result.Id.ShouldBe(_id);
_result.InternalString.ShouldBe(_internalString);
_result.OrderDate.ShouldBe(_orderDate);
_result.Status.ShouldBe(_status);
}

[Test]
public void Simple_does_not_populate_Items()
{
var actual = OrderBuilder.Simple().Build();
actual.ShouldBeOfType<Order>();
actual.Items.ShouldBeNull();
}

[Test]
public void Simple_returns_an_OrderBuilder() => OrderBuilder.Simple().ShouldBeOfType<OrderBuilder>();

[Test]
public void Typical_populates_Items()
{
var actual = OrderBuilder.Typical().Build();
actual.ShouldBeOfType<Order>();
actual.Items.ShouldNotBeNull();
}

[Test]
public void Typical_returns_an_OrderBuilder() => OrderBuilder.Typical().ShouldBeOfType<OrderBuilder>();

[OneTimeSetUp]
public void SetUp()
{
_id = Guid.NewGuid();
_orderDate = DateTime.Now;
_status = OrderStatus.Processing;
_internalString = Guid.NewGuid().ToString();

_result = OrderBuilder
.Typical()
.WithId(_id)
.WithInternalString(_internalString)
.WithOrderDate(_orderDate)
.WithStatus(_status)
.Build();
}
}

0 comments on commit 9ceeaf5

Please sign in to comment.