DEV Community

Cover image for Acceptance Testing Strategies (Part 1): Inside-Out and Outside-In
Outdated Dev
Outdated Dev

Posted on

Acceptance Testing Strategies (Part 1): Inside-Out and Outside-In

Hello there!👋🧔‍♂️ Have you ever wondered how to ensure your software actually does what users need it to do? That's where acceptance testing comes in! This is Part 1 of a two-part series. Here we cover what acceptance tests are, plus two core strategies: inside-out and outside-in. In Part 2 we'll cover feature tests, the testing pyramid, and best practices for combining all three.

Whether you're building a new feature or improving an existing system, understanding these strategies will help you write tests that truly validate your software meets business requirements and user expectations.

Overview

Acceptance testing is all about verifying that your software meets business requirements and delivers value to users. It's the bridge between "does it work?" and "does it solve the right problem?"

In this part we focus on two approaches:

  1. Inside-Out Testing - Start from core business logic and work outward
  2. Outside-In Testing - Start from user interface and work inward

In Part 2 we'll add Feature Tests (complete features end-to-end) and how to combine all three. Code examples use C# and xUnit; the same ideas apply with Jest, Playwright, or any stack; focus on the strategy, not the syntax.

What are Acceptance Tests?

Acceptance tests verify whether your software meets specified business requirements and user expectations. They are often implemented as end-to-end tests (UI or API), but the important part is the intent: they validate behavior from the user's or product's perspective and that the system delivers the expected business value. They can also be automated at API or service level when that's enough to prove acceptance.

Think of them as answering the question: "Can a user (or system) actually accomplish what they need to do with this software?" Acceptance testing can include manual exploratory testing or user acceptance testing (UAT) with real users; automation (e.g. the examples below) supports and extends that by making key scenarios repeatable and regression-safe.

Key Characteristics

  • User-focused: Tests from the user's (or product's) perspective
  • Business-aligned: Validates business requirements and acceptance criteria
  • Scenario-based: Often test complete workflows (end-to-end or across key layers)
  • Living documentation: Serves as executable documentation of system behavior
  • Traceable: Can be tied to user stories and acceptance criteria for coverage and reporting

Acceptance criteria and BDD

Many teams express acceptance criteria in Given/When/Then form (from BDD) and automate them with tools like SpecFlow (C#), Cucumber, or similar. The scenarios become executable acceptance tests. That helps QA and product define what to verify and developers / Software Development Engineers in Test implement how to verify it. The code examples below use the same Given/When/Then structure in comments; in practice you might write them as Gherkin and bind to step definitions.

Same scenario in Gherkin (SpecFlow / Cucumber)

The same flow can be written as a Gherkin scenario. Tools like SpecFlow (C#) or Cucumber generate executable tests from these; step definitions call into your app or UI:

Feature: Online purchase

  Scenario: User can complete an online purchase
    Given the user is on the product page for "laptop-123"
    When the user adds the product to the cart
    And the user proceeds to checkout
    And the user enters payment details "4111111111111111", "12/25", "123"
    And the user completes the purchase
    Then the order is confirmed
    And the user receives a confirmation email
Enter fullscreen mode Exit fullscreen mode

Step definitions (e.g. in SpecFlow) would map each line to code (navigate to product, click Add to Cart, fill form, assert confirmation). This keeps acceptance criteria readable for non-developers while staying executable.

Example structure (xUnit)

[Fact]
public void User_Can_Complete_Online_Purchase()
{
    // Given: User is on the product page
    var productPage = new ProductPage();
    productPage.NavigateToProduct("laptop-123");

    // When: User adds product to cart and proceeds to checkout
    productPage.AddToCart();
    var checkoutPage = productPage.GoToCheckout();
    productPage.EnterPaymentDetails("4111111111111111", "12/25", "123");
    checkoutPage.CompletePurchase();

    // Then: Order is confirmed and user receives confirmation
    var confirmationPage = checkoutPage.GetConfirmationPage();
    Assert.True(confirmationPage.IsOrderConfirmed());
    Assert.True(confirmationPage.HasConfirmationEmail());
}
Enter fullscreen mode Exit fullscreen mode

1. Inside-Out Testing

What is Inside-Out Testing?

Inside-out testing starts from the core business logic and works outward to the user interface. You build and test the system's internal components first, then integrate them with external interfaces.

Testing Flow:

Core Business Logic → Service Layer → API Layer → UI Layer
Enter fullscreen mode Exit fullscreen mode

Think of it like building a house: you start with the foundation (business logic), then add walls (services), then windows and doors (APIs), and finally paint and decorations (UI).

Characteristics

  • Bottom-up approach: Start with unit tests, build up to integration tests
  • Component-first: Test individual components in isolation
  • Technical focus: Emphasizes code quality and technical correctness
  • Developer-driven: Primarily led by the development team

Example Implementation

// 1. Start with core business logic (Unit Tests)
[Fact]
public void CalculateOrderTotal_WithValidItems_ReturnsCorrectTotal()
{
    // Arrange
    var orderService = new OrderService();
    var order = new Order
    {
        Items = new List<OrderItem>
        {
            new OrderItem { ProductId = 1, Quantity = 2, Price = 10.00m },
            new OrderItem { ProductId = 2, Quantity = 1, Price = 15.00m }
        }
    };

    // Act
    var total = orderService.CalculateTotal(order);

    // Assert
    Assert.Equal(35.00m, total);
}

// 2. Test service layer (Integration Tests)
[Fact]
public void OrderService_ProcessOrder_WithValidData_CreatesOrder()
{
    // Arrange
    var orderService = new OrderService(_orderRepository, _paymentService);
    var orderRequest = new CreateOrderRequest
    {
        CustomerId = 123,
        Items = new List<OrderItemRequest> { /* ... */ }
    };

    // Act
    var result = orderService.ProcessOrder(orderRequest);

    // Assert
    Assert.True(result.IsSuccess);
    Assert.NotNull(result.OrderId);
}

// 3. Test API layer (API Tests)
[Fact]
public async Task OrdersController_CreateOrder_WithValidRequest_ReturnsCreatedOrder()
{
    // Arrange
    var client = _factory.CreateClient();
    var orderRequest = new CreateOrderRequest { /* ... */ };

    // Act
    var response = await client.PostAsJsonAsync("/api/orders", orderRequest);

    // Assert
    response.EnsureSuccessStatusCode();
    var order = await response.Content.ReadFromJsonAsync<Order>();
    Assert.NotNull(order);
}
Enter fullscreen mode Exit fullscreen mode

Advantages

  • Technical Quality: Ensures solid technical foundation
  • Component Isolation: Tests components independently
  • Fast Feedback: Unit tests provide immediate feedback
  • Code Coverage: Easy to achieve high code coverage
  • Developer Control: Developers have full control over test implementation

Disadvantages

  • Business Gap: May miss business requirements
  • Integration Issues: Late discovery of integration problems
  • User Perspective: Doesn't validate user experience
  • End-to-End Gaps: May miss system-level issues

When to Use Inside-Out Testing

  • Technical debt reduction projects
  • Legacy system modernization
  • API-first development
  • Microservices architecture
  • When technical quality is the primary concern

2. Outside-In Testing

What is Outside-In Testing?

Outside-in testing starts from the user interface and works inward to the core business logic. You focus on user scenarios and business requirements first, then implement the necessary technical components.

Testing Flow:

User Scenarios → UI Tests → API Tests → Service Tests → Unit Tests
Enter fullscreen mode Exit fullscreen mode

Think of it like designing a car: you start with how the driver will interact with it (steering wheel, pedals, dashboard), then design the engine and systems to support that experience.

Characteristics

  • Top-down approach: Start with user scenarios, drill down to implementation
  • User-centric: Focuses on user experience and business value
  • Behavior-driven: Emphasizes behavior over implementation
  • Collaborative: Involves business stakeholders and users

Example Implementation

// 1. Start with user scenarios (Acceptance Tests)
[Fact]
public void Customer_Can_Purchase_Product_Online()
{
    // Given: Customer is browsing the online store
    var storePage = new OnlineStorePage();
    storePage.NavigateToStore();

    // When: Customer selects a product and completes purchase
    var productPage = storePage.SelectProduct("laptop-123");
    productPage.AddToCart();

    var checkoutPage = productPage.ProceedToCheckout();
    checkoutPage.EnterShippingAddress("123 Main St", "New York", "NY", "10001");
    checkoutPage.EnterPaymentInfo("4111111111111111", "12/25", "123");
    checkoutPage.CompletePurchase();

    // Then: Customer receives order confirmation
    var confirmationPage = checkoutPage.GetConfirmationPage();
    Assert.True(confirmationPage.IsOrderConfirmed());
    Assert.True(confirmationPage.HasOrderNumber());
}

// 2. Test API endpoints (API Tests)
[Fact]
public async Task Checkout_ProcessOrder_WithValidData_ReturnsOrderConfirmation()
{
    // Arrange
    var client = _factory.CreateClient();
    var checkoutRequest = new CheckoutRequest
    {
        CartId = "cart-123",
        ShippingAddress = new Address { /* ... */ },
        PaymentInfo = new PaymentInfo { /* ... */ }
    };

    // Act
    var response = await client.PostAsJsonAsync("/api/checkout", checkoutRequest);

    // Assert
    response.EnsureSuccessStatusCode();
    var confirmation = await response.Content.ReadFromJsonAsync<OrderConfirmation>();
    Assert.NotNull(confirmation.OrderId);
    Assert.True(confirmation.IsConfirmed);
}

// 3. Test service layer (Service Tests)
[Fact]
public void CheckoutService_ProcessOrder_WithValidData_CreatesOrderAndSendsConfirmation()
{
    // Arrange
    var checkoutService = new CheckoutService(_orderService, _paymentService, _emailService);
    var checkoutRequest = new CheckoutRequest { /* ... */ };

    // Act
    var result = checkoutService.ProcessOrder(checkoutRequest);

    // Assert
    Assert.True(result.IsSuccess);
    Assert.NotNull(result.OrderId);
    _emailService.Received(1).SendOrderConfirmation(Arg.Any<string>());
}
Enter fullscreen mode Exit fullscreen mode

Advantages

  • Business Alignment: Ensures business requirements are met
  • User Focus: Validates user experience and satisfaction
  • Early Validation: Identifies issues from user perspective early
  • Stakeholder Engagement: Involves business stakeholders in testing
  • End-to-End Coverage: Ensures complete user journey works

Disadvantages

  • Slow Feedback: UI tests are slower than unit tests
  • Brittle Tests: UI tests can be fragile and hard to maintain
  • Technical Debt: May lead to quick-and-dirty implementations
  • Complex Setup: Requires more complex test environment setup

Who typically drives what

Outside-in and feature-level acceptance tests are often specified or co-written by QA and product (e.g. as scenarios or acceptance criteria), with developers or Software Development Engineers in Test implementing the automation. Inside-out tests are usually owned by developers. Making this collaboration explicit helps teams assign ownership and avoid gaps (e.g. "nobody wrote the acceptance test for this story").

When to Use Outside-In Testing

  • New feature development
  • User experience critical applications
  • Business-critical functionality
  • When business requirements are unclear
  • Customer-facing applications

Next: In Part 2 we cover Feature Tests, the testing pyramid, how to compare the three strategies, and best practices for combining them.

Top comments (0)