Sitemap
Sherry walker

Sherry walker

How to Use Python to Implement Soft Chain Assertion

12 min readFeb 24, 2026

--

Test automation efficiency hinges on one critical factor: how quickly you can identify all failures in a single test run. Traditional assertions stop at the first error, forcing you to fix issues one at a time and rerun tests repeatedly.

This guide demonstrates how to use Python to implement soft chain assertion techniques that collect all failures and report them together, dramatically reducing debugging time and improving test suite efficiency.

Press enter or click to view image in full size
use python implement soft chain assertion

Understanding the Critical Difference Between Hard and Soft Assertions

Python’s standard assert statement implements a fail-fast approach. When an assertion fails, Python raises an AssertionError immediately, halting execution of the current test function.

This behavior serves well for critical preconditions but becomes a significant bottleneck when validating multiple independent properties.

Consider testing an e-commerce product page. You need to verify the product title, price, availability status, rating, shipping information, and image URLs.

With hard assertions, a failed title check prevents you from discovering that the price format is also incorrect. You fix the title, rerun the test, discover the price issue, fix it, rerun again, and eventually find the shipping data problem. This iterative cycle wastes substantial development time.

For teams developing complex applications, whether working with a mobile app development company in New York or building web platforms, efficient testing becomes even more critical as application complexity increases.

Research from test automation surveys indicates that teams implementing soft assertions effectively identify issues in 30% fewer test runs. When each test execution reveals multiple failures instead of just one, your debugging workflow becomes exponentially more efficient.

Why Fail-Fast Assertions Create Development Bottlenecks

The fail-fast model creates several challenges in modern test automation:

Incomplete Diagnostic Information: You discover failures sequentially rather than comprehensively. A test suite with 50 assertions across 10 objects might require 15–20 test runs to uncover all issues if each run only reveals 2–3 failures before stopping.

Reduced Developer Productivity: According to industry data, test automation can reduce feedback response time to developers by up to 80% when implemented effectively. However, this benefit diminishes significantly when tests must run multiple times to reveal all failures.

Wasted CI/CD Resources: In continuous integration pipelines, each test run consumes computational resources and time. Multiple runs to discover all failures multiply these costs unnecessarily.

Delayed Bug Discovery: When testing complex data structures or API responses with dozens of fields, sequential failure discovery can delay bug identification by hours or even days.

The Comprehensive Soft Assertion Solution

Soft assertions fundamentally change the testing paradigm by collecting all assertion failures during test execution and reporting them together at the end. Instead of stopping at the first failure, the test continues evaluating all conditions, building a complete failure report.

This approach provides development teams with a comprehensive view of application health in a single test run. You immediately see that five out of twenty API response fields are incorrect, rather than discovering them one at a time through repeated test executions.

The implementation strategy varies across Python testing frameworks, but the underlying principle remains consistent: defer raising the AssertionError until all assertions have been evaluated.

Python Soft Assertion Libraries: A Detailed Comparison

Python lacks native soft assertion support, but the ecosystem provides several excellent libraries that fill this gap. Each offers distinct advantages depending on your testing framework and requirements.

pytest-check: The Industry Standard for pytest

The pytest-check plugin has emerged as the most widely adopted solution for pytest users. Released with ongoing maintenance through 2025, it provides both context manager and direct function call approaches.

Installation:

pip install pytest-check

Key Advantages:

  • Native integration with pytest’s reporting system
  • Supports both context manager and direct function syntax
  • Actively maintained with regular updates through 2025
  • Comprehensive failure reporting with pseudo-tracebacks
  • Configurable output limits for performance optimization

Usage Example:

import pytest_check as check
def test_user_profile_complete():
user_data = {
"username": "testuser",
"email": "wrong@example.com",
"age": 17,
"verified": False,
"role": "guest"
}

# Context manager approach
with check:
assert user_data["username"] == "expecteduser"
assert user_data["email"] == "expected@example.com"
assert user_data["age"] >= 18
assert user_data["verified"] is True
assert user_data["role"] == "admin"

All five assertions execute regardless of failures, and pytest-check generates a detailed report showing each failed condition with actual vs. expected values.

Alternative Direct Function Approach:

import pytest_check as check
def test_api_response():
response = make_api_call()

check.equal(response.status_code, 200, "Status code mismatch")
check.is_true(response.json()["success"], "Success flag not true")
check.greater(len(response.json()["data"]), 0, "Empty data array")
check.is_in("id", response.json()["data"][0], "ID field missing")

assertpy: Fluent Assertions with Soft Capability

The assertpy library provides a fluent assertion API inspired by AssertJ from the Java ecosystem. It offers excellent readability through method chaining and includes robust soft assertion support.

Installation:

pip install assertpy

Key Advantages:

  • Highly readable fluent API
  • Works with pytest, unittest, and standalone
  • Extensive assertion methods for various data types
  • Built-in support for extracting and validating nested data
  • Clean syntax for complex validation chains

Implementation:

from assertpy import assert_that, soft_assertions
def test_product_data():
product = {
"name": "Widget",
"price": 150.00,
"in_stock": False,
"category": "electronics",
"rating": 3.5
}

with soft_assertions():
assert_that(product["name"]).is_equal_to("Super Widget")
assert_that(product["price"]).is_less_than(100.00)
assert_that(product["in_stock"]).is_true()
assert_that(product["category"]).is_equal_to("tools")
assert_that(product["rating"]).is_greater_than_or_equal_to(4.0)

Chaining for Cleaner Code:

from assertpy import assert_that, soft_assertions
def test_list_validation():
user_ids = [101, 102, 103, 104, 105]

with soft_assertions():
assert_that(user_ids).is_not_empty()\
.is_length(5)\
.contains(101, 105)\
.does_not_contain(999)

smart-assertions: Explicit Verification Pattern

The smart-assertions library implements a verification pattern where assertions are checked immediately but results are collected until explicitly verified.

Installation:

pip install smart-assertions

Usage:

from smart_assertions import soft_assert, verify_expectations
def test_temperature_readings():
readings = get_sensor_data()

soft_assert(readings["temp"] > 0, "Temperature below zero")
soft_assert(readings["humidity"] < 100, "Humidity exceeds maximum")
soft_assert(readings["pressure"] > 980, "Pressure too low")
soft_assert(readings["altitude"] >= 0, "Invalid altitude")

verify_expectations() # Raises AssertionError with all failures

soft-assert: Context-Based Verification

This library provides a clean context manager syntax specifically designed for pytest integration.

Get Sherry walker’s stories in your inbox

Join Medium for free to get updates from this writer.

Installation:

pip install soft-assert

Implementation:

from soft_assert import check, verify
def test_database_records():
with verify():
for record in get_records():
check(record.id is not None, f"Missing ID in record")
check(record.created_at is not None, f"Missing timestamp")
check(len(record.data) > 0, f"Empty data field")

Step-by-Step Implementation with pytest-check

For most Python developers using pytest, pytest-check represents the optimal choice due to its active maintenance, comprehensive features, and seamless integration. Here’s a complete implementation guide.

Installation and Setup

Install pytest-check in your project environment:

pip install pytest-check

No additional configuration is required. The plugin automatically integrates with pytest once installed.

Basic Implementation Pattern

import pytest_check as check
def test_user_registration_form():
"""Validate all fields in user registration response"""
registration_data = submit_registration_form()

with check:
assert registration_data["status"] == "success"
assert "user_id" in registration_data
assert registration_data["email_sent"] is True
assert registration_data["verification_token"] is not None
assert len(registration_data["username"]) >= 3

Advanced Usage with API Testing

import pytest_check as check
import requests
def test_api_endpoint_comprehensive():
"""Test multiple aspects of API response"""
response = requests.get("https://api.example.com/users/123")

# Hard assertion for critical precondition
assert response.status_code == 200, "API request failed"

# Soft assertions for response validation
data = response.json()

with check:
assert "id" in data, "Missing user ID"
assert "name" in data, "Missing user name"
assert "email" in data, "Missing email"
assert data["active"] is True, "User not active"
assert len(data["permissions"]) > 0, "No permissions"

# Additional validation using direct functions
check.is_instance(data["id"], int)
check.is_not_none(data["created_at"])
check.greater_equal(data["login_count"], 0)

Loop-Based Validation

import pytest_check as check
def test_multiple_user_records():
"""Validate collection of user records"""
users = fetch_user_batch()

for user in users:
with check:
assert user.email is not None, f"User {user.id} missing email"
assert "@" in user.email, f"Invalid email for {user.id}"
assert user.age >= 18, f"User {user.id} under age limit"
assert user.verified is True, f"User {user.id} not verified"

Creating a Custom Soft Assertion Context Manager

For teams requiring specialized behavior or working without pytest, implementing a custom soft assertion context manager provides complete control.

class SoftAssertions:
def __init__(self):
self.failures = []

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
if self.failures:
failure_messages = "\n".join(
f"{i+1}. {msg}" for i, msg in enumerate(self.failures)
)
raise AssertionError(
f"Soft assertion failures ({len(self.failures)}):\n{failure_messages}"
)
return True

def check(self, condition, message="Assertion failed"):
if not condition:
import traceback
tb = traceback.extract_stack()[-2]
location = f"{tb.filename}:{tb.lineno}"
self.failures.append(f"{message} (at {location})")
# Usage
def test_custom_soft_assertions():
with SoftAssertions() as soft:
soft.check(2 + 2 == 4, "Basic math failed")
soft.check("hello".upper() == "HELLO", "String conversion failed")
soft.check([1, 2, 3][-1] == 3, "List indexing failed")

Real-World Implementation Patterns and Best Practices

Strategic Combination of Hard and Soft Assertions

The most effective test suites strategically combine both assertion types. Use hard assertions for critical preconditions and soft assertions for validating outcomes.

import pytest_check as check
import requests
def test_user_dashboard():
# Hard assertion: must be logged in to proceed
login_response = authenticate_user()
assert login_response.status_code == 200, "Login failed"

# Hard assertion: must receive valid token
token = login_response.json()["token"]
assert token is not None, "No authentication token"

# Now soft-assert all dashboard elements
dashboard = fetch_dashboard(token)

with check:
assert dashboard["user_name"] == "Expected Name"
assert dashboard["account_balance"] > 0
assert len(dashboard["recent_transactions"]) >= 5
assert dashboard["notification_count"] >= 0
assert "profile_image_url" in dashboard

Validating Complex Data Structures

Soft assertions excel when validating objects with multiple properties:

import pytest_check as check
def test_order_object():
order = create_test_order()

with check:
# Order header validation
assert order.order_id is not None
assert order.customer_id == 12345
assert order.total_amount == 299.99
assert order.status == "pending"

# Items validation
assert len(order.items) == 3
assert sum(item.quantity for item in order.items) == 5

# Shipping validation
assert order.shipping_address.zip_code == "12345"
assert order.shipping_method == "express"

# Payment validation
assert order.payment_method == "credit_card"
assert order.payment_status == "authorized"

Form Validation Testing

import pytest_check as check
from selenium import webdriver
def test_registration_form_validation():
driver = webdriver.Chrome()
driver.get("https://example.com/register")

# Fill form with invalid data
fill_registration_form(driver, invalid_data=True)
submit_form(driver)

# Verify all validation messages appear
with check:
assert is_visible(driver, "#username-error")
assert is_visible(driver, "#email-error")
assert is_visible(driver, "#password-error")
assert is_visible(driver, "#terms-error")

# Verify error message content
assert get_text(driver, "#username-error") == "Username too short"
assert get_text(driver, "#email-error") == "Invalid email format"
assert get_text(driver, "#password-error") == "Password too weak"

Performance Optimization and Configuration

pytest-check Configuration Options

pytest-check provides several command-line options for controlling output and performance:

# Limit pseudo-traceback generation (improves performance)
pytest --check-max-tb=1
# Limit reported failures per test
pytest --check-max-report=10
# Stop test after specified number of failures
pytest --check-max-fail=20

Add these to pytest.ini for permanent configuration:

[pytest]
addopts = --check-max-tb=1 --check-max-report=15

Managing Large Test Suites

When testing hundreds of properties, structure assertions logically:

import pytest_check as check
def test_large_api_response():
response = fetch_large_dataset()

# Group related checks
with check: # Basic structure
assert "data" in response
assert "metadata" in response
assert "pagination" in response

with check: # Data validation
assert len(response["data"]) > 0
assert all("id" in item for item in response["data"])

with check: # Metadata validation
assert response["metadata"]["total"] > 0
assert response["metadata"]["version"] == "2.0"

Common Pitfalls and How to Avoid Them

Overusing Soft Assertions

Not every test should use soft assertions. Avoid them when:

  1. Testing sequential operations where each step depends on the previous
  2. Validating critical security checks
  3. Testing database transactions where rollback is needed on first failure
  4. Setup and teardown operations

Bad Example:

def test_sequential_operations():
with check: # DON'T DO THIS
assert create_user() is not None
assert assign_permissions() is True
assert send_welcome_email() is True

Good Example:

def test_sequential_operations():
# Hard assertions for sequential dependencies
user = create_user()
assert user is not None, "User creation failed"

permissions = assign_permissions(user)
assert permissions is True, "Permission assignment failed"

# Soft assertions for independent validations
with check:
assert user.email_verified is False
assert user.role == "standard"
assert len(user.permissions) == 5

Inadequate Failure Messages

Generic failure messages waste debugging time:

# Poor practice
with check:
assert data["value"] > 100 # What value? What's expected?
# Better approach
with check:
assert data["temperature"] > 100, \
f"Temperature {data['temperature']} below minimum threshold 100"
assert data["pressure"] > 980, \
f"Pressure {data['pressure']} below safe minimum 980 hPa"

Mixing Frameworks Incorrectly

Different soft assertion libraries have different verification triggers:

# Won't work as expected
from assertpy import soft_assertions
from smart_assertions import verify_expectations
with soft_assertions():  # Uses assertpy's context manager
soft_assert(x == 1) # Uses smart-assertions (wrong!)
# Failures won't be collected properly

Integration with CI/CD Pipelines

Soft assertions integrate seamlessly with continuous integration systems. Configure pytest to generate detailed reports:

Modern development teams, including those working with mobile app development teams, rely on robust CI/CD pipelines to maintain code quality. Soft assertions enhance these workflows by providing comprehensive test feedback in every pipeline run.

# .github/workflows/test.yml
name: Test Suite
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: |
pip install pytest pytest-check pytest-html
- name: Run tests with detailed reporting
run: |
pytest --html=report.html --self-contained-html \
--check-max-tb=5 --check-max-report=20
- name: Upload test results
uses: actions/upload-artifact@v2
with:
name: test-results
path: report.html

Measuring the Impact on Test Efficiency

Track these metrics to quantify soft assertion benefits:

  1. Average failures per test run: Should increase initially as more issues are discovered simultaneously
  2. Test run iterations to fix: Should decrease significantly (target: 30–50% reduction)
  3. Time to fix all failures: Total time from discovery to resolution
  4. CI/CD pipeline duration: May increase slightly per run but decrease overall due to fewer runs

Example measurement approach:

import pytest_check as check
import time
class TestMetrics:
failure_count = 0

@pytest.fixture(autouse=True)
def track_failures(self):
TestMetrics.failure_count = 0
yield
print(f"\nTest discovered {TestMetrics.failure_count} failures")

def test_with_metrics(self):
with check:
TestMetrics.failure_count += 1 if not condition1 else 0
assert condition1

TestMetrics.failure_count += 1 if not condition2 else 0
assert condition2

Advanced Techniques and Patterns

Parameterized Testing with Soft Assertions

Combine pytest’s parametrization with soft assertions:

import pytest
import pytest_check as check
@pytest.mark.parametrize("user_id,expected_role", [
(1, "admin"),
(2, "user"),
(3, "moderator"),
(4, "guest")
])
def test_user_roles(user_id, expected_role):
user = fetch_user(user_id)

with check:
assert user is not None, f"User {user_id} not found"
assert user.role == expected_role, \
f"User {user_id} role mismatch"
assert user.active is True, f"User {user_id} not active"

Fixture-Based Soft Assertion Setup

Create reusable soft assertion fixtures:

import pytest
import pytest_check as check
@pytest.fixture
def soft_check():
"""Provides configured soft assertion context"""
return check
def test_with_fixture(soft_check):
data = get_test_data()

with soft_check:
assert data["field1"] == "expected1"
assert data["field2"] == "expected2"

Data-Driven Validation

import pytest_check as check
def test_validate_all_users():
users = fetch_all_users()

validation_rules = [
("email", lambda u: "@" in u.email, "Invalid email format"),
("age", lambda u: u.age >= 18, "Under minimum age"),
("verified", lambda u: u.verified is True, "Not verified"),
]

for user in users:
with check:
for field, validator, message in validation_rules:
assert validator(user), \
f"User {user.id}: {message}"

Troubleshooting Common Issues

Soft Assertions Not Collecting Failures

Problem: Assertions still stop at first failure

Solutions:

# Ensure you're using the check context manager
with check: # Correct
assert condition
# Or direct check functions
check.equal(a, b) # Correct
# Not this:
assert condition # Still hard assertion outside context

Unclear Failure Reports

Problem: Can’t identify which assertion failed

Solution: Add descriptive messages

with check:
assert user.age >= 18, \
f"User {user.id} age {user.age} below minimum"
assert user.email_verified, \
f"User {user.id} email not verified"

Performance Degradation

Problem: Tests running slowly with many soft assertions

Solutions:

# Limit traceback generation
pytest --check-max-tb=1
# Group related assertions strategically
with check: # Lightweight checks
assert field1 == value1
assert field2 == value2
# Heavy operations outside soft assertions
computed_value = expensive_operation()
with check:
assert computed_value == expected

Frequently Asked Questions

Can I use soft assertions with unittest?

Yes, libraries like assertpy work with unittest. However, integration is less seamless than with pytest:

import unittest
from assertpy import assert_that, soft_assertions
class TestExample(unittest.TestCase):
def test_with_soft_assertions(self):
with soft_assertions():
assert_that(1).is_equal_to(2)
assert_that("hello").is_equal_to("world")

Do soft assertions affect test execution time?

The performance impact is minimal. Modern soft assertion libraries are highly optimized. The overhead of collecting failures is negligible compared to the time saved by eliminating multiple test runs.

Should I always use soft assertions?

No. Strategic use is key:

  • Use soft assertions: For validating multiple independent properties of the same object
  • Use hard assertions: For critical preconditions, sequential dependencies, and security checks

How many assertions should I include in one soft assertion block?

There’s no hard limit, but best practices suggest:

  • Group 5–15 related assertions per block
  • Break very large validation sets into logical groups
  • Consider readability and debugging efficiency

Can soft assertions be used in production code?

Soft assertions are designed for testing. Python’s assert statements are removed when running with optimization (python -O), so they should never be used for production validation logic.

Conclusion: Transforming Your Test Automation Efficiency

Learning to use Python to implement soft chain assertion represents a fundamental shift in test automation strategy. By collecting all failures in a single test run, you provide your development team with comprehensive diagnostic information, dramatically reducing debugging cycles and improving overall productivity.

The pytest-check library offers the most robust solution for pytest users, combining ease of use with powerful features and active maintenance. For teams using other frameworks or requiring custom behavior, assertpy provides excellent fluent API support, while custom implementations offer complete control.

The key to success lies in strategic application: combine hard assertions for critical preconditions with soft assertions for comprehensive validation. Structure your tests to maximize information gathering while maintaining clarity and debuggability. With these techniques, your test automation suite will become more efficient, your debugging cycles shorter, and your software quality higher.

Start implementing soft assertions in your next test suite and measure the impact on your development workflow. The reduction in test run iterations and the comprehensive failure reports will quickly demonstrate why this technique has become a standard practice in professional test automation.

--

--