EasyTdd.Generators
EasyTdd.Generators works in tandem with EasyTdd, a Visual Studio extension that enhances test-driven development (available at Visual Studio Marketplace). This package leverages a .NET incremental source generator to create builders and FluentMock classes from templates used by the EasyTdd extension. The builder generator handles property setters, constructor parameters, and a combination of both. It also supports generic parameters. The FluentMock generator is a wrapper for the well-known mocking framework Moq. It generates specific Setup and Verify methods for each method and property of the target type, allowing setup and verification with less code and less pain. If the EasyTdd settings are not available, it uses default settings. For more information, visit easytdd.dev.
Release notes
6.0.2
- Fixed BuilderGenerator and FluentMockGenerator failing with
CS8785 and a missing System.Text.Json, Version=10.0.0.0 assembly under .NET SDK 9.
- Bundled Scriban's complete
netstandard2.0 dependency chain for both NuGet consumption and analyzer project references. These remain private generation-time dependencies; consuming projects do not acquire compiler or JSON package dependencies.
- Preserved Scriban JSON template functions, generated APIs, Roslyn 4.3.1 compiler compatibility, settings version
6 and template compatibility version 6.0.
- Added clean-consumer regression checks pinned to SDK 9.0.308 and 10.0.401 for both consumption modes, including custom templates using JSON functions.
6.0.1
- Aligned with the Visual Studio extension patch that fixes settings and default-template migration when opening solutions configured for version
0.5.
- Retained Roslyn 4.3.1 compiler compatibility and unchanged generated Builder and FluentMock APIs. Settings remain at version
6 and default templates at compatibility version 6.0; the source generator does not migrate files.
6.0.0
- Adopted three-part release versions, aligned with the Visual Studio extension and MCP server.
- Settings now use a single major-version string (
"6"); template compatibility versions use major/minor (6.0). Generated Builder and FluentMock APIs are unchanged by this version transition.
- Upgrade existing configuration with the matching extension or MCP server; the source generator does not migrate files.
- Lowered the Roslyn API dependency to 4.3.1 so generators load in compiler hosts running Roslyn 4.3.1 or newer, including Roslyn 4.13.0 build agents. This fixes missing
BuilderForAttribute and FluentMockForAttribute errors when an older compiler rejects the generator. Roslyn remains a private build-time dependency supplied by the compiler host.
Back up .easyTdd before migration and update tools that share it together. Older VSIX/MCP releases cannot safely read settings version 6. To downgrade, restore the backup and previous tool/package versions. Settings compatibility follows release-major versions; template compatibility follows major/minor versions, so patches retain both.
0.5.3.2
- Made Roslyn a private build-time dependency. Compiler packages and their
System.* dependencies no longer flow into consumer projects, preventing NU1605 package downgrades caused by EasyTdd.Generators.
- Kept generator support libraries bundled with the analyzer; Roslyn is supplied by the compiler host.
- Ensured the package includes its shared domain assembly when built outside a solution.
0.5.3
- Corrected FluentMock generation for inherited and hidden members, including properties declared with
new and namespace-qualified method types.
- Corrected tuple type rendering in generated builder and FluentMock signatures.
- Updated Scriban to 7.5.0 to address dependency security advisories.
0.5.2
- Added default settings for solutions without a
.easyTdd directory.
- Fixed builder and FluentMock generation for inherited members, hidden properties, and indexers.
0.5.0.4
- Excluded properties without public setters from builder generation.
0.5.0.3
- Prevented duplicate generation of overridden members in builders and tests.
0.5.0.1
- Maintained backward compatibility with version 0.4.0.5.
0.5.0.0
- Added the incremental FluentMock source generator.
0.4.0.5
- Fixed builder generation for same-named classes in different namespaces.
0.4.0.3
- Fixed template resolution when a template in the root
.easyTdd directory shares a name with one in a subdirectory.
0.4.0.0
- Introduced the incremental builder source generator, with version numbering aligned to the EasyTdd Visual Studio extension.
How it works?
The EasyTdd.Generators package, implemented as an analyzer that includes an IIncrementalGenerator, accesses the .easyTdd folder containing configurations created by the EasyTdd Visual Studio extension. Utilizing templates and settings within this folder, the generator efficiently produces the builder and fluentMock classes.
Examples
Incremental builder
Target class SampleClass.cs
public class SampleClass<T>
where T : class
{
public SampleClass(T value, int id)
{
Value = value;
Id = id;
}
public T Value { get; }
public int Id { get; }
public string Name { get; set; }
public string Description { get; set; }
}
Builder class SampleClassBuilder.cs
[BuilderFor(typeof(SampleClass<>))]
public partial class SampleClassBuilder<T>
{
public SampleClassBuilder<T> Default()
{
return new SampleClassBuilder<T>(
default,
1,
"Jovaras",
"Augalas"
);
}
}
Generated class SampleClassBuilder.g.cs
public partial class SampleClassBuilder<T>
where T : class
{
private Func<T> _value;
private Func<int> _id;
private Func<string> _name;
private Func<string> _description;
public static implicit operator EasyTdd.Generators.ForTestingByNugetPackageReference.SampleClass<T>(SampleClassBuilder<T> builder) => builder.Build();
public SampleClassBuilder(
Func<T> value,
Func<int> id,
Func<string> name,
Func<string> description)
{
_value = value;
_id = id;
_name = name;
_description = description;
}
public SampleClassBuilder(
T value,
int id,
string name,
string description)
{
_value = () => value;
_id = () => id;
_name = () => name;
_description = () => description;
}
public EasyTdd.Generators.ForTestingByNugetPackageReference.SampleClass<T> Build()
{
return new EasyTdd.Generators.ForTestingByNugetPackageReference.SampleClass<T>(
_value(),
_id())
{
Name = _name(),
Description = _description()
};
}
public SampleClassBuilder<T> WithValue(Func<T> value)
{
_value = value;
return this;
}
public SampleClassBuilder<T> WithValue(T value)
{
return WithValue(() => value);
}
public SampleClassBuilder<T> WithId(Func<int> value)
{
_id = value;
return this;
}
public SampleClassBuilder<T> WithId(int value)
{
return WithId(() => value);
}
public SampleClassBuilder<T> WithName(Func<string> value)
{
_name = value;
return this;
}
public SampleClassBuilder<T> WithName(string value)
{
return WithName(() => value);
}
public SampleClassBuilder<T> WithDescription(Func<string> value)
{
_description = value;
return this;
}
public SampleClassBuilder<T> WithDescription(string value)
{
return WithDescription(() => value);
}
}
Incremental FluentMock
Target interface ISomeInterface.cs
public interface ISomeInterface
{
SomeResultType Resolve(int id, string value, SomeOtherResultType someOtherResultType);
}
FluentMock class SomeInterfaceMock.cs
[EasyTdd.Generators.FluentMockFor(typeof(ISomeInterface))]
public partial class SomeInterfaceMock
{
public static SomeInterfaceMock Create()
{
return new SomeInterfaceMock();
}
}
Generated FluentMock class SomeInterfaceMock.g.cs
// <auto-generated>
// This code was generated by EasyTdd.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated. Use a partial class for additional behavior.
// </auto-generated>
#nullable enable
using Moq;
using System;
namespace EasyTdd.Generators.ForTestingByProjectReference.ForDebug
{
public partial class SomeInterfaceMock : Mock<EasyTdd.Generators.ForTestingByProjectReference.ForDebug.ISomeInterface>
{
public SomeInterfaceMock() : base(MockBehavior.Strict)
{ }
public SomeInterfaceMock SetupResolve(
Func<EasyTdd.Generators.ForTestingByProjectReference.ForDebug.SomeResultType> result,
Func<int, bool>? isId = null,
Func<string, bool>? isValue = null,
Func<EasyTdd.Generators.ForTestingByProjectReference.ForDebug.SomeOtherResultType, bool>? isSomeOtherResultType = null,
bool verifiable = true)
{
var setup = Setup(
x => x.Resolve(
It.Is<int>(fromMock => isId == null || isId(fromMock)),
It.Is<string>(fromMock => isValue == null || isValue(fromMock)),
It.Is<EasyTdd.Generators.ForTestingByProjectReference.ForDebug.SomeOtherResultType>(fromMock => isSomeOtherResultType == null || isSomeOtherResultType(fromMock))
)
)
.Returns(result);
if (verifiable)
{
setup.Verifiable();
}
return this;
}
public SomeInterfaceMock SetupResolve(
Func<int, string, EasyTdd.Generators.ForTestingByProjectReference.ForDebug.SomeOtherResultType, EasyTdd.Generators.ForTestingByProjectReference.ForDebug.SomeResultType> result,
Func<int, bool>? isId = null,
Func<string, bool>? isValue = null,
Func<EasyTdd.Generators.ForTestingByProjectReference.ForDebug.SomeOtherResultType, bool>? isSomeOtherResultType = null,
bool verifiable = true)
{
var setup = Setup(
x => x.Resolve(
It.Is<int>(fromMock => isId == null || isId(fromMock)),
It.Is<string>(fromMock => isValue == null || isValue(fromMock)),
It.Is<EasyTdd.Generators.ForTestingByProjectReference.ForDebug.SomeOtherResultType>(fromMock => isSomeOtherResultType == null || isSomeOtherResultType(fromMock))
)
)
.Returns(result);
if (verifiable)
{
setup.Verifiable();
}
return this;
}
public SomeInterfaceMock SetupResolveSequence(
IEnumerable<Func<EasyTdd.Generators.ForTestingByProjectReference.ForDebug.SomeResultType>> results,
Func<int, bool>? isId = null,
Func<string, bool>? isValue = null,
Func<EasyTdd.Generators.ForTestingByProjectReference.ForDebug.SomeOtherResultType, bool>? isSomeOtherResultType = null)
{
var sequenceSetup =
SetupSequence(
x => x.Resolve(
It.Is<int>(fromMock => isId == null || isId(fromMock)),
It.Is<string>(fromMock => isValue == null || isValue(fromMock)),
It.Is<EasyTdd.Generators.ForTestingByProjectReference.ForDebug.SomeOtherResultType>(fromMock => isSomeOtherResultType == null || isSomeOtherResultType(fromMock))
)
);
foreach (var result in results)
{
sequenceSetup.Returns(result);
}
return this;
}
public SomeInterfaceMock VerifyResolve(
Func<int, bool>? isId = null,
Func<string, bool>? isValue = null,
Func<EasyTdd.Generators.ForTestingByProjectReference.ForDebug.SomeOtherResultType, bool>? isSomeOtherResultType = null,
Func<Times>? times = null)
{
Verify(
x => x.Resolve(
It.Is<int>(fromMock => isId == null || isId(fromMock)),
It.Is<string>(fromMock => isValue == null || isValue(fromMock)),
It.Is<EasyTdd.Generators.ForTestingByProjectReference.ForDebug.SomeOtherResultType>(fromMock => isSomeOtherResultType == null || isSomeOtherResultType(fromMock))
),
times ?? Times.AtLeastOnce
);
return this;
}
}
}
FluentMock usage
public class UsageTest
{
[Test]
public void Test()
{
var expectedResult1 = new SomeResultType();
var expectedResult2 = new SomeResultType();
var mock = SomeInterfaceMock
.Create()
.SetupResolve(
() => expectedResult1,
isId: x => x == 1
)
.SetupResolve(
() => expectedResult2,
isId: x => x == 2
);
mock
.Object
.Resolve(
1,
"",
new SomeOtherResultType()
)
.Should()
.Be(expectedResult1);
mock
.Object
.Resolve(
2,
"",
new SomeOtherResultType()
)
.Should()
.Be(expectedResult2);
}
}