Skip to content

adriamontoto/object-mother-pattern

Repository files navigation

βš’οΈ Object Mother Pattern

CI Pipeline Coverage Pipeline Package Version Supported Python Versions Package Downloads Project Documentation

The Object Mother Pattern is a Python 🐍 package that simplifies and standardizes the creation of test πŸ§ͺ data. Instead of hand-writing random values, invalid values, range boundaries, dates, identifiers, URLs, credit cards, and other fixtures in every test module, you use reusable object mother classes with consistent validation behavior.

Table of Contents

πŸ”Ό Back to top



πŸ“₯ Installation

You can install Object Mother Pattern using pip:

pip install object-mother-pattern

You can install the companion AI-agent skill from skills.sh with Vercel's skills CLI:

npx skills add adriamontoto/object-mother-pattern

Review the skill source in skills/object-mother-pattern before installing it in sensitive environments.

πŸ”Ό Back to top



πŸ“š Documentation

The root README is the entry point. Deeper guides live in this repository and are linked here:

This project's DeepWiki documentation is also available for generated repository navigation.

πŸ”Ό Back to top



⚑ Quick Start

Use mothers directly in tests when you need valid values:

from object_mother_pattern import (
    BooleanMother,
    DateMother,
    FloatMother,
    IntegerMother,
    StringMother,
)
from object_mother_pattern.mothers.identifiers.uuid import UuidV4Mother

integer = IntegerMother.create(min=-4, max=15)
amount = FloatMother.create(min=0, max=100, decimals=2)
enabled = BooleanMother.create()
username = StringMother.lowercase(min_length=8, max_length=16)
date = DateMother.create()
uuid = UuidV4Mother.create()

Pass an explicit value when a test needs a known valid value and you still want the mother to validate it:

from object_mother_pattern import IntegerMother

assert IntegerMother.create(value=7) == 7

Generate values outside an accepted range when testing validation failures:

from datetime import date

from object_mother_pattern import DateMother

value = DateMother.out_of_range(
    start_date=date(year=2025, month=1, day=1),
    end_date=date(year=2025, month=1, day=31),
)

πŸ”Ό Back to top



🧩 Core Ideas

Most mothers follow the same pattern:

  • create(value=...) validates and returns an explicit value.
  • create(...) without value generates a valid random value.
  • Dedicated helpers such as empty(), of_length(), positive(), odd(), or out_of_range() express common test shapes.
  • invalid_type() generates a value with the wrong type for negative-path tests.
  • Collection mothers can compose other mothers with callables such as item_mother, key_mother, and value_mother.
from object_mother_pattern.models import DictMother, ListMother
from object_mother_pattern import IntegerMother, StringMother

values = ListMother.of_length(length=3, item_mother=IntegerMother.positive)
payload = DictMother.of_length(
    length=2,
    key_mother=lambda: StringMother.lowercase(min_length=4, max_length=8),
    value_mother=IntegerMother.create,
)

πŸ”Ό Back to top



πŸ€” Why Object Mothers?

Object mothers help tests say what kind of value they need instead of repeating how that value is built. A hardcoded literal such as 'john@example.com', '550e8400-e29b-41d4-a716-446655440000', or 42 can be perfectly valid, but when every test invents its own literals the setup becomes duplicated, inconsistent, and harder to update when a validation rule changes.

Use object mothers when the exact value does not matter and the test only needs a value that satisfies a shape:

from object_mother_pattern import IntegerMother
from object_mother_pattern.mothers.internet import EmailAddressMother

email = EmailAddressMother.create()
quantity = IntegerMother.positive()

assert '@' in email
assert quantity > 0

This keeps the test focused on the behavior under test. The mother owns the details of valid email generation, positive integer generation, invalid types, ranges, and special formats. If those rules evolve, tests that only need "a valid email" or "a positive integer" do not need to be rewritten.

Hardcoded values are still the best choice when the value is the point of the test:

  • Use fixed literals for exact serialization, snapshots, URLs, JSON, SQL, error messages, and UI copy.
  • Use explicit boundary values for deliberate limits such as minimum length, maximum length, zero, one, empty strings, first date, last date, or a known invalid format.
  • Use a known literal when reproducing a specific bug.
  • Use create(value=...) when you want an explicit value but still want the mother to validate that it belongs to the expected shape.
from object_mother_pattern import IntegerMother, StringMother

assert StringMother.create(value='invoice-paid') == 'invoice-paid'
assert IntegerMother.create(value=10) == 10

The practical rule is simple: generate values for invariant-based tests, hardcode values for exact examples and intentional limits.

πŸ”Ό Back to top



πŸ“ƒ Available Mothers

The package includes mothers for:

Category Examples
Models BaseMother, EnumerationMother, ListMother, DictMother
Primitives BooleanMother, BytesMother, FloatMother, IntegerMother, StringMother
Dates DateMother, DatetimeMother, StringDateMother, StringDatetimeMother, timezone mothers
Identifiers UUID mothers, Spanish identifiers, world/country code identifiers
Internet URLs, domains, hosts, ports, IP addresses, networks, MAC addresses, email addresses, user agents
Money IBANs, credit cards by brand, Bitcoin wallet values
People Full names, usernames, passwords
Extra Text snippets

See docs/catalog/README.md for a deeper feature map and import guidance.

πŸ”Ό Back to top



πŸ§ͺ Testing Patterns

Use explicit values when an assertion depends on exact output:

from object_mother_pattern import StringMother

assert StringMother.create(value='known-value') == 'known-value'

Use generated values when the test only cares about invariants:

from object_mother_pattern import IntegerMother

value = IntegerMother.positive()

assert value > 0

Use invalid helpers for negative-path coverage:

from pytest import raises

from object_mother_pattern import IntegerMother

with raises(TypeError):
    IntegerMother.create(value=IntegerMother.invalid_type())

More guidance is available in docs/testing/README.md.

πŸ”Ό Back to top



πŸŽ„ Real-Life Case: Christmas Detector Service

This service checks whether a date falls within a Christmas holiday range. Tests use DateMother to create dates inside and outside the accepted range without duplicating date-generation logic.

from datetime import date
from object_mother_pattern import DateMother


class ChristmasDetectorService:
    def __init__(self) -> None:
        self._christmas_start = date(year=2024, month=12, day=24)
        self._christmas_end = date(year=2025, month=1, day=6)

    def is_christmas(self, today: date) -> bool:
        return self._christmas_start <= today <= self._christmas_end


christmas_detector_service = ChristmasDetectorService()


def test_christmas_detector_is_christmas() -> None:
    today = DateMother.create(
        start_date=date(year=2024, month=12, day=25),
        end_date=date(year=2025, month=1, day=6),
    )

    assert christmas_detector_service.is_christmas(today=today)


def test_christmas_detector_is_not_christmas() -> None:
    today = DateMother.out_of_range(
        start_date=date(year=2024, month=12, day=24),
        end_date=date(year=2025, month=1, day=6),
    )

    assert christmas_detector_service.is_christmas(today=today) is False

πŸ”Ό Back to top



πŸ§‘β€πŸ”§ Creating Your Own Object Mother

You can extend the functionality of this library by subclassing the BaseMother class. A custom mother should validate explicit values and generate valid values when no value is provided.

from random import randint

from object_mother_pattern.models import BaseMother


class PositiveIntegerMother(BaseMother[int]):
    @classmethod
    def create(cls, *, value: int | None = None) -> int:
        if value is not None:
            if type(value) is not int:
                raise TypeError('PositiveIntegerMother value must be an integer.')

            if value <= 0:
                raise ValueError('PositiveIntegerMother value must be positive.')

            return value

        return randint(a=1, b=100)

More detail is available in docs/usage/README.md.

πŸ”Ό Back to top



🀝 Contributing

We love community help! Before you open an issue or pull request, please read:

Thank you for helping make βš’οΈ Object Mother Pattern package awesome! 🌟

πŸ”Ό Back to top



πŸ”‘ License

This project is licensed under the terms of the MIT license.

πŸ”Ό Back to top

About

The Object Mother Pattern is a Python 🐍 package that simplifies and standardizes the creation of test πŸ§ͺ objects.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

1 watching

Forks

Sponsor this project

Contributors