Skip to content
Merged
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
10 changes: 9 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ The library includes comprehensive testing helpers for consumers:

**CdkTestHelper** (`CdkTestHelper.cs`):
- `CreateTestStack()`: Creates CDK app and stack with test environment
- `CreateTestStackMinimal()`: Creates only the stack (app created internally)
- `CreateTestStack<TStack>()`: Creates custom stack types directly using generics and reflection
- `CreateTestStackMinimal()`: Creates only the stack (app created internally)
- `CreateTestStackMinimal<TStack>()`: Creates custom stack types with app created internally
- `GetTestAssetPath()`: Resolves test asset paths relative to executing assembly
- `CreatePropsBuilder()`: Creates fluent builder with test defaults

Expand All @@ -105,6 +107,12 @@ The library includes comprehensive testing helpers for consumers:
- Always create CDK templates AFTER adding constructs to stacks
- Use `Template.FromStack(stack)` after construct creation, not before

**Generic Stack Creation**:
- Use `CreateTestStack<TStack>()` and `CreateTestStackMinimal<TStack>()` for custom stack types
- These methods use `Activator.CreateInstance` with reflection to instantiate custom stacks
- Eliminates the need to create unused base stacks when testing custom stack implementations
- Supports any stack type that inherits from `Amazon.CDK.Stack` and follows the standard constructor pattern

## Development Practices

### Code Style
Expand Down
36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ The library includes comprehensive testing helpers to make it easy to unit test

### Testing Helpers Overview

- **`CdkTestHelper`**: Reduces boilerplate for creating test stacks and props
- **`CdkTestHelper`**: Reduces boilerplate for creating test stacks and props (including custom stack types)
- **`LambdaFunctionConstructAssertions`**: Extension methods for asserting Lambda resources
- **`LambdaFunctionConstructPropsBuilder`**: Fluent builder for creating test props

Expand Down Expand Up @@ -200,6 +200,40 @@ public void MyStack_ShouldCreateLegacyFunction()
}
```

### Example: Testing Custom Stack Types

For projects with custom stack implementations (inheriting from `Stack`), you can use the generic methods to create your custom stack types directly:

```csharp
[Fact]
public void MyCustomStack_ShouldCreateInfrastructure()
{
// Custom stack props (could be your LightsaberStackProps, InfraStackProps, etc.)
var customProps = new MyCustomStackProps
{
Env = new Environment { Account = "123456789012", Region = "us-east-1" },
Context = new MyContext { /* your custom context */ },
Tags = new Dictionary<string, string> { { "Environment", "test" } }
};

// Create your custom stack type directly - no unused base stack needed!
var customStack = CdkTestHelper.CreateTestStackMinimal<MyCustomStack>("test-stack", customProps);

// Your custom stack is ready to use
var template = Template.FromStack(customStack);

// Verify your custom stack's infrastructure
template.ShouldHaveLambdaFunction("my-function-test");
// ... other assertions
}
```

**Benefits of Generic Stack Creation:**
- **No Workarounds**: Create custom stack types directly instead of creating unused base stacks
- **Type Safety**: Get strongly-typed stack instances with full IntelliSense support
- **Clean Tests**: Eliminate boilerplate while maintaining flexibility
- **Real-World Ready**: Works with any custom stack implementation

### Test Asset Management

Create a `TestAssets` folder in your test project and place your Lambda deployment packages there:
Expand Down
33 changes: 33 additions & 0 deletions src/LayeredCraft.Cdk.Constructs/Testing/CdkTestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,23 @@ public static (App app, Stack stack) CreateTestStack(string stackName, IStackPro
return (app, stack);
}

/// <summary>
/// Creates a test CDK app and custom stack type with the specified props.
/// This method uses reflection to instantiate the custom stack type.
/// Note: You must create the Template.FromStack(stack) after adding constructs to the stack.
/// </summary>
/// <typeparam name="TStack">The custom stack type that inherits from Stack</typeparam>
/// <param name="stackName">The name of the test stack</param>
/// <param name="stackProps">Custom stack props implementing IStackProps</param>
/// <returns>Tuple containing the app and the custom stack instance</returns>
public static (App app, TStack stack) CreateTestStack<TStack>(string stackName, IStackProps stackProps)
where TStack : Stack
{
var app = new App();
var stack = (TStack)Activator.CreateInstance(typeof(TStack), app, stackName, stackProps)!;
return (app, stack);
}

/// <summary>
/// Creates a test CDK stack (app is created internally).
/// Note: You must create the Template.FromStack(stack) after adding constructs to the stack.
Expand Down Expand Up @@ -80,6 +97,22 @@ public static Stack CreateTestStackMinimal(string stackName, IStackProps stackPr
return stack;
}

/// <summary>
/// Creates a test CDK custom stack type with the specified props (app is created internally).
/// This method uses reflection to instantiate the custom stack type.
/// Note: You must create the Template.FromStack(stack) after adding constructs to the stack.
/// </summary>
/// <typeparam name="TStack">The custom stack type that inherits from Stack</typeparam>
/// <param name="stackName">The name of the test stack</param>
/// <param name="stackProps">Custom stack props implementing IStackProps</param>
/// <returns>The custom stack instance for testing</returns>
public static TStack CreateTestStackMinimal<TStack>(string stackName, IStackProps stackProps)
where TStack : Stack
{
var (_, stack) = CreateTestStack<TStack>(stackName, stackProps);
return stack;
}

/// <summary>
/// Gets the path to a test asset relative to the executing assembly location.
/// This ensures test assets can be found regardless of the current working directory.
Expand Down
108 changes: 108 additions & 0 deletions test/LayeredCraft.Cdk.Constructs.Tests/Testing/TestingHelpersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,4 +291,112 @@ public void CdkTestHelper_ShouldWorkWithRealWorldCustomStackProps()

template.ShouldHaveLambdaFunction("real-world-function-test");
}

[Fact]
public void CdkTestHelper_ShouldCreateGenericStackType()
{
var customProps = new Amazon.CDK.StackProps
{
Env = new Amazon.CDK.Environment
{
Account = "111222333444",
Region = "ca-central-1"
},
Description = "Generic stack test"
};

// Create a custom stack type using the generic method
var (app, stack) = CdkTestHelper.CreateTestStack<TestCustomStack>("generic-test-stack", customProps);

app.Should().NotBeNull();
stack.Should().NotBeNull();
stack.Should().BeOfType<TestCustomStack>();
stack.StackName.Should().Be("generic-test-stack");
stack.Account.Should().Be("111222333444");
stack.Region.Should().Be("ca-central-1");

// Verify custom stack functionality
var customStack = (TestCustomStack)stack;
customStack.CustomProperty.Should().Be("CustomValue");
}

[Fact]
public void CdkTestHelper_ShouldCreateGenericStackTypeMinimal()
{
var customProps = new Amazon.CDK.StackProps
{
Env = new Amazon.CDK.Environment
{
Account = "555777999111",
Region = "eu-central-1"
},
Tags = new Dictionary<string, string>
{
{ "TestType", "Generic" },
{ "Framework", "CDK" }
}
};

// Create custom stack type using the minimal generic method
var stack = CdkTestHelper.CreateTestStackMinimal<TestCustomStack>("minimal-generic-stack", customProps);

stack.Should().NotBeNull();
stack.Should().BeOfType<TestCustomStack>();
stack.StackName.Should().Be("minimal-generic-stack");
stack.Account.Should().Be("555777999111");
stack.Region.Should().Be("eu-central-1");

// Verify custom stack functionality
var customStack = (TestCustomStack)stack;
customStack.CustomProperty.Should().Be("CustomValue");
}

[Fact]
public void CdkTestHelper_ShouldWorkWithGenericStackAndConstructs()
{
var customProps = new Amazon.CDK.StackProps
{
Env = new Amazon.CDK.Environment
{
Account = "888999000111",
Region = "us-east-2"
}
};

// Create custom stack and add constructs to it
var stack = CdkTestHelper.CreateTestStackMinimal<TestCustomStack>("integration-test", customProps);

var props = CdkTestHelper.CreatePropsBuilder(AssetPathExtensions.GetTestLambdaZipPath())
.WithFunctionName("generic-function")
.WithEnvironmentVariable("STACK_TYPE", "CustomStack")
.Build();

_ = new LambdaFunctionConstruct(stack, "GenericConstruct", props);

// Create template AFTER adding constructs to the stack
var template = Template.FromStack(stack);

// Verify everything works together
template.ShouldHaveLambdaFunction("generic-function-test");
template.ShouldHaveEnvironmentVariables(new Dictionary<string, string>
{
{ "ENVIRONMENT", "test" },
{ "STACK_TYPE", "CustomStack" }
});

// Verify custom stack properties
var customStack = (TestCustomStack)stack;
customStack.CustomProperty.Should().Be("CustomValue");
}
}

// Test helper class for generic stack testing
public class TestCustomStack : Amazon.CDK.Stack

Copilot AI Jul 3, 2025

Copy link

Choose a reason for hiding this comment

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

The TestCustomStack helper class is declared at the top level without a namespace and shares the test file with your test cases. Consider moving it into its own file and/or adding an appropriate namespace to avoid potential naming collisions and improve clarity.

Copilot uses AI. Check for mistakes.
{
public string CustomProperty { get; }

public TestCustomStack(Amazon.CDK.App scope, string id, Amazon.CDK.IStackProps props) : base(scope, id, props)
{
CustomProperty = "CustomValue";
}
}