Supercharge Your Testing with GitHub Copilot:

Supercharge Your Testing with GitHub Copilot:

Generating MSTest DataRows for an Onion Style Architecture

Testing is the backbone of reliable software development, yet it's often the most time-consuming aspect of building robust applications. Suppose you're working with an Onion Architecture (also known as Clean Architecture) service and need to test both your controller and business layers thoroughly. In that case, GitHub Copilot can be your secret weapon for generating comprehensive MSTest suites with DataRows that efficiently cover multiple test scenarios.

In this comprehensive guide, we'll explore how to leverage GitHub Copilot's AI-powered code generation to create sophisticated test suites that validate your onion architecture's controller and business layers. We'll cover practical techniques, provide real-world examples, and share expert tips to maximize your testing productivity.

Understanding Onion Architecture Testing Challenges

Before diving into Copilot techniques, let's briefly review the testing challenges in onion architecture:

  1. Controller Layer: HTTP concerns, request/response mapping, validation, authorization
  2. Business Layer: Core business logic, domain rules, orchestration between services
  3. Data Layer: Repository patterns, data access, entity mapping

Each layer requires different testing approaches, and DataRows help us test multiple scenarios without code duplication.

Setting Up Your Testing Environment

First, ensure your test project has the necessary NuGet packages:

<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.0.4" />
<PackageReference Include="MSTest.TestFramework" Version="3.0.4" />
<PackageReference Include="Moq" Version="4.20.69" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.0" />

Effective Prompts for Controller Layer Testing

Basic Controller Test Generation

Prompt:

Generate MSTest with DataRows for testing a UserController PostUser action that accepts CreateUserRequest and returns UserResponse. Test cases should include valid user creation, invalid email format, missing required fields, and duplicate username scenarios. Use AAA pattern and include proper assertions.

Generated Result:

[TestClass]
public class UserControllerTests
{
    private UserController _controller;
    private Mock<IUserService> _mockUserService;

    [TestInitialize]
    public void Setup()
    {
        _mockUserService = new Mock<IUserService>();
        _controller = new UserController(_mockUserService.Object);
    }

    [TestMethod]
    [DataRow("john.doe@email.com", "John", "Doe", "johndoe", true, "User created successfully")]
    [DataRow("invalid-email", "John", "Doe", "johndoe", false, "Invalid email format")]
    [DataRow("", "John", "Doe", "johndoe", false, "Email is required")]
    [DataRow("john.doe@email.com", "", "Doe", "johndoe", false, "First name is required")]
    [DataRow("john.doe@email.com", "John", "Doe", "", false, "Username is required")]
    [DataRow("existing@email.com", "John", "Doe", "existinguser", false, "Username already exists")]
    public async Task PostUser_WithVariousInputs_ReturnsExpectedResults(
        string email, string firstName, string lastName, string username, 
        bool shouldSucceed, string expectedMessage)
    {
        // Arrange
        var request = new CreateUserRequest
        {
            Email = email,
            FirstName = firstName,
            LastName = lastName,
            Username = username
        };

        if (shouldSucceed)
        {
            _mockUserService.Setup(x => x.CreateUserAsync(It.IsAny<CreateUserRequest>()))
                .ReturnsAsync(new UserResponse { Id = 1, Message = expectedMessage });
        }
        else
        {
            _mockUserService.Setup(x => x.CreateUserAsync(It.IsAny<CreateUserRequest>()))
                .ThrowsAsync(new ValidationException(expectedMessage));
        }

        // Act
        var result = shouldSucceed 
            ? await _controller.PostUser(request) as OkObjectResult
            : await _controller.PostUser(request) as BadRequestObjectResult;

        // Assert
        Assert.IsNotNull(result);
        if (shouldSucceed)
        {
            var response = result.Value as UserResponse;
            Assert.AreEqual(expectedMessage, response.Message);
        }
        else
        {
            Assert.IsTrue(result.Value.ToString().Contains(expectedMessage));
        }
    }
}

Advanced Authorization Testing

Prompt:

Create MSTest DataRows for testing authorization in ProductController GetProducts action. Include test cases for admin role (full access), user role (own products only), guest (no access), expired token, and invalid token scenarios. Mock IAuthorizationService and include proper HTTP status code assertions.

Business Layer Testing Strategies

Domain Logic Validation

Prompt:

Generate MSTest with DataRows for OrderService CalculateTotal method in business layer. Test scenarios: normal order with multiple items, order with discounts, order with tax calculations, empty order, order with invalid quantities, and order with expired promotions. Include edge cases and boundary conditions.

Generated Result:

[TestClass]
public class OrderServiceTests
{
    private OrderService _orderService;
    private Mock<IDiscountRepository> _mockDiscountRepo;
    private Mock<ITaxCalculator> _mockTaxCalculator;

    [TestInitialize]
    public void Setup()
    {
        _mockDiscountRepo = new Mock<IDiscountRepository>();
        _mockTaxCalculator = new Mock<ITaxCalculator>();
        _orderService = new OrderService(_mockDiscountRepo.Object, _mockTaxCalculator.Object);
    }

    [TestMethod]
    [DataRow(new double[] { 10.00, 20.00, 15.00 }, new int[] { 2, 1, 3 }, 0.0, 0.10, 96.25)]
    [DataRow(new double[] { 100.00 }, new int[] { 1 }, 10.0, 0.08, 97.20)]
    [DataRow(new double[] { 50.00, 30.00 }, new int[] { 1, 2 }, 15.0, 0.12, 100.80)]
    [DataRow(new double[] { }, new int[] { }, 0.0, 0.0, 0.0)]
    [DataRow(new double[] { 25.00 }, new int[] { 0 }, 0.0, 0.0, 0.0)]
    public void CalculateTotal_WithVariousOrderConfigurations_ReturnsCorrectTotal(
        double[] itemPrices, int[] quantities, double discountPercent, 
        double taxRate, double expectedTotal)
    {
        // Arrange
        var orderItems = new List<OrderItem>();
        for (int i = 0; i < itemPrices.Length; i++)
        {
            if (i < quantities.Length && quantities[i] > 0)
            {
                orderItems.Add(new OrderItem 
                { 
                    Price = itemPrices[i], 
                    Quantity = quantities[i] 
                });
            }
        }

        var order = new Order { Items = orderItems };

        _mockDiscountRepo.Setup(x => x.GetApplicableDiscount(It.IsAny<Order>()))
            .Returns(discountPercent);
        _mockTaxCalculator.Setup(x => x.CalculateTax(It.IsAny<double>()))
            .Returns<double>(amount => amount * taxRate);

        // Act
        var result = _orderService.CalculateTotal(order);

        // Assert
        Assert.AreEqual(expectedTotal, result, 0.01);
    }
}

Complex Business Rules Testing

Prompt:

Create comprehensive MSTest DataRows for InventoryService ProcessRestockOrder method. Test scenarios include: sufficient stock levels, insufficient stock with backorder, priority customer handling, seasonal restrictions, vendor availability, quantity limits, and concurrent stock updates. Use realistic business scenarios and include proper exception handling tests.

Advanced Copilot Techniques

Using Context-Aware Prompts

Effective Strategy: Start your prompts with existing code context to get more accurate results.

Prompt:

Based on this existing UserService interface:
```csharp
public interface IUserService
{
    Task<UserResponse> CreateUserAsync(CreateUserRequest request);
    Task<UserResponse> GetUserByIdAsync(int id);
    Task<bool> DeleteUserAsync(int id);
}

Generate MSTest with DataRows for UserService implementation testing all methods. Include positive cases, negative cases, async exception handling, and edge cases. Mock dependencies appropriately.

### Chaining Prompts for Complex Scenarios

**Initial Prompt:**

Generate MSTest DataRows for PaymentService ProcessPayment method with basic scenarios: successful payment, insufficient funds, invalid card, expired card.

**Follow-up Prompt:**

Extend the previous PaymentService tests with additional DataRows for: fraud detection triggers, currency conversion scenarios, refund processing, partial payments, and payment gateway timeout handling.

### Integration Testing with Copilot

**Prompt:**

Create MSTest integration tests with DataRows for e-commerce checkout workflow spanning controller and business layers. Test complete user journey: add to cart, apply discount, calculate shipping, process payment, update inventory. Use TestServer and realistic data scenarios.

## Best Practices and Pro Tips

### 1. Structure Your Prompts Effectively

**Good Prompt Structure:**
- Context (what you're testing)
- Specific requirements (DataRows, scenarios)
- Technical constraints (frameworks, patterns)
- Expected outcomes (assertions, exceptions)

### 2. Leverage Copilot for Test Data Generation

**Prompt:**

Generate realistic test data objects for e-commerce testing including: valid/invalid credit cards, international addresses, product catalogs with various categories, user profiles with different roles, and order histories with edge cases.

### 3. Use Copilot for Mock Setup

**Prompt:**

Generate Moq setup code for testing OrderController that depends on IOrderService, IPaymentService, and IInventoryService. Include different return scenarios for each dependency and proper verify calls.

### 4. Exception Testing Patterns

**Prompt:**

Create MSTest DataRows for testing exception scenarios in UserRegistrationService. Include: ArgumentNullException for null inputs, ValidationException for invalid data, DuplicateUserException for existing users, and ServiceUnavailableException for external service failures.

## Complex Real-World Example

Let's create a comprehensive test suite for an order processing system:

**Prompt:**

Generate a complete MSTest suite with DataRows for OrderProcessingService in an e-commerce onion architecture. Include controller tests for OrderController endpoints and business layer tests for OrderService. Cover scenarios: new order creation, order status updates, inventory validation, payment processing, shipping calculations, order cancellation, refund processing, and concurrent order handling. Use realistic e-commerce data and include proper mocking of dependencies.

**Generated Result Structure:**
```csharp
[TestClass]
public class OrderProcessingIntegrationTests
{
    // Controller Layer Tests
    [TestMethod]
    [DataRow("valid-order.json", 201, "Order created successfully")]
    [DataRow("invalid-payment.json", 400, "Payment method invalid")]
    [DataRow("insufficient-inventory.json", 409, "Insufficient stock")]
    public async Task CreateOrder_WithVariousScenarios_ReturnsExpectedResults(
        string testDataFile, int expectedStatusCode, string expectedMessage)
    {
        // Implementation here
    }

    // Business Layer Tests
    [TestMethod]
    [DataRow(OrderStatus.Pending, OrderStatus.Processing, true)]
    [DataRow(OrderStatus.Processing, OrderStatus.Shipped, true)]
    [DataRow(OrderStatus.Shipped, OrderStatus.Delivered, true)]
    [DataRow(OrderStatus.Delivered, OrderStatus.Processing, false)]
    public void UpdateOrderStatus_WithStatusTransitions_ValidatesBusinessRules(
        OrderStatus currentStatus, OrderStatus newStatus, bool shouldSucceed)
    {
        // Implementation here
    }
}

Debugging and Refinement Tips

1. Iterative Improvement

Start with basic tests and refine:

Initial Prompt:

Generate basic MSTest for UserController Login method

Refinement Prompt:

Add DataRows to the previous UserController Login test for: valid credentials, invalid password, non-existent user, locked account, expired password, and rate limiting scenarios

2. Copilot-Assisted Test Maintenance

Prompt:

Update the existing UserService tests to include new DataRows for: two-factor authentication scenarios, OAuth login, password reset workflows, and account verification steps

Performance Considerations

1. Efficient Test Data

Prompt:

Generate MSTest DataRows using object arrays for complex scenarios in ProductCatalogService. Optimize for test execution speed while maintaining comprehensive coverage of product search, filtering, sorting, and pagination scenarios.

2. Parallel Test Execution

Prompt:

Create thread-safe MSTest DataRows for concurrent testing of OrderService methods. Include proper test isolation and avoid shared state issues.

Conclusion

GitHub Copilot transforms the traditionally tedious task of writing comprehensive test suites into an efficient, AI-assisted process. By crafting effective prompts and leveraging Copilot's understanding of testing patterns, you can generate robust MSTest suites with DataRows that thoroughly validate both your controller and business layers.

The key to success lies in:

  1. Clear, contextual prompting that provides Copilot with sufficient information
  2. Iterative refinement of generated tests to match your specific requirements
  3. Strategic use of DataRows to maximize test coverage with minimal code duplication
  4. Proper separation of concerns between controller and business layer tests

Remember that while Copilot excels at generating boilerplate test code and common patterns, you should always review and customize the generated tests to ensure they align with your specific business requirements and testing standards.

Start implementing these techniques in your next project, and you'll find that comprehensive testing becomes not just manageable, but genuinely productive and even enjoyable. Your onion architecture will be more robust, your code more reliable, and your development velocity significantly increased.

Happy testing with GitHub Copilot!