Personas: Named Data Profiles

Personas let you define named data profiles that combine generation modes, field overrides, and behaviors into reusable configurations. Instead of thinking in technical terms like "edge mode" or "hostile mode," think in user archetypes: "happy path user," "free tier customer," or "problematic account."

Quick Start

# Define personas
FakeDataDSL::Personas.define(:happy_path) do
  mode :random
  override "User", email_verified: true, subscription: "premium"
end

FakeDataDSL::Personas.define(:problematic) do
  mode :hostile
  override "User", name: "'; DROP TABLE users; --"
end

# Use personas
user = FakeDataDSL.as(:happy_path) { FakeDataDSL.generate("User") }
hacker = FakeDataDSL.as(:problematic) { FakeDataDSL.generate("User") }

Defining Personas

Basic Persona

FakeDataDSL::Personas.define(:standard_user) do
  mode :random
end

With Overrides

FakeDataDSL::Personas.define(:premium_customer) do
  mode :random

  override "User",
    subscription: "premium",
    email_verified: true,
    created_at: 1.year.ago

  override "Account",
    plan: "enterprise",
    seats: 50
end

With Behaviors

FakeDataDSL::Personas.define(:slow_connection) do
  mode :random

  behavior :latency, min: 500, max: 2000  # 500-2000ms delays
  behavior :timeout, probability: 0.1     # 10% timeout rate
end

Built-in Personas

FakeDataDSL includes several built-in personas:

:happy_path

Standard, valid data that represents the ideal user journey.

FakeDataDSL.as(:happy_path) do
  user = FakeDataDSL.generate("User")
  # All fields valid, all required fields present
  # Represents a "normal" user
end

:edge_case

Data at the boundaries of valid ranges.

FakeDataDSL.as(:edge_case) do
  user = FakeDataDSL.generate("User")
  # age might be 0 or 150
  # name might be 1 character or 255 characters
  # Tests boundary conditions
end

:minimal

Only required fields, everything else nil/empty.

FakeDataDSL.as(:minimal) do
  user = FakeDataDSL.generate("User")
  # Only id, name, email (required fields)
  # All optional fields are nil
end

:maximal

All fields populated, arrays at max length.

FakeDataDSL.as(:maximal) do
  user = FakeDataDSL.generate("User")
  # All optional fields populated
  # Arrays at maximum allowed length
end

:hacker

Malicious input attempting to exploit vulnerabilities.

FakeDataDSL.as(:hacker) do
  user = FakeDataDSL.generate("User")
  # SQL injection in text fields
  # XSS attempts in HTML-allowed fields
  # Path traversal in file fields
end

:international

Data with international characters and formats.

FakeDataDSL.as(:international) do
  user = FakeDataDSL.generate("User")
  # Names in various scripts (Chinese, Arabic, Cyrillic)
  # International phone formats
  # Non-ASCII email addresses
end

Using Personas

Block Syntax

FakeDataDSL.as(:premium_customer) do
  user = FakeDataDSL.generate("User")
  orders = FakeDataDSL.generate_many("Order", 5)
end

Method Chaining

user = FakeDataDSL.generate("User").as(:happy_path)
orders = FakeDataDSL.generate_many("Order", 10).as(:edge_case)

In Tests

RSpec.describe "User Registration" do
  FakeDataDSL::Personas.each do |persona_name|
    context "as #{persona_name}" do
      let(:user_data) { FakeDataDSL.as(persona_name) { generate("User") } }

      it "handles registration" do
        result = UserRegistration.call(user_data)
        # Verify behavior for each persona
      end
    end
  end
end

Advanced Configuration

Persona Inheritance

FakeDataDSL::Personas.define(:base_customer) do
  mode :random
  override "User", role: "customer"
end

FakeDataDSL::Personas.define(:new_customer, extends: :base_customer) do
  override "User", 
    created_at: 1.day.ago,
    first_purchase: nil
end

FakeDataDSL::Personas.define(:loyal_customer, extends: :base_customer) do
  override "User",
    created_at: 2.years.ago,
    purchase_count: 50,
    loyalty_tier: "gold"
end

Conditional Overrides

FakeDataDSL::Personas.define(:seasonal_shopper) do
  mode :random

  override "User" do |context|
    if context[:month].in?([11, 12])  # Holiday season
      { purchase_frequency: "high", budget: 1000..5000 }
    else
      { purchase_frequency: "low", budget: 50..200 }
    end
  end
end

Dynamic Personas

FakeDataDSL::Personas.define(:load_test) do |scale: 1|
  mode :random

  override "User",
    connections: 10 * scale,
    requests_per_minute: 100 * scale
end

# Usage
FakeDataDSL.as(:load_test, scale: 10) { ... }

YAML Configuration

Define personas in configuration files:

# config/fake_data_dsl.yml
personas:
  happy_path:
    mode: random
    overrides:
      User:
        email_verified: true
        subscription: premium

  free_user:
    mode: random
    overrides:
      User:
        subscription: free
        created_at: 2.years.ago

  problematic:
    mode: edge
    overrides:
      User:
        name: "'; DROP TABLE users; --"
        email: test@test.test

  international:
    mode: random
    locale: multi
    overrides:
      User:
        name_locale: [zh, ar, ru, ja, ko]

# Environment-specific
test:
  default_persona: happy_path

development:
  default_persona: random

Persona Combinations

Multiple Personas

# Combine persona traits
FakeDataDSL.as(:premium_customer, :international) do
  user = FakeDataDSL.generate("User")
  # Premium subscription + international formatting
end

Persona with Mode Override

# Persona as base, mode overrides specifics
FakeDataDSL.as(:premium_customer).mode(:edge) do
  user = FakeDataDSL.generate("User")
  # Premium overrides + edge case values for other fields
end

Persona with Additional Overrides

FakeDataDSL.as(:happy_path).override(active: false) do
  user = FakeDataDSL.generate("User")
  # Happy path + explicitly inactive
end

Testing Patterns

Test All Personas

RSpec.describe "API Endpoint" do
  FakeDataDSL::Personas.all.each do |persona|
    context "with #{persona.name} persona" do
      let(:data) { FakeDataDSL.as(persona.name) { generate("Request") } }

      it "responds successfully" do
        post "/api/endpoint", params: data
        expect(response).to have_http_status(:ok)
      end
    end
  end
end

Persona Matrix

RSpec.describe "Security" do
  # Test dangerous personas don't cause issues
  [:hacker, :problematic, :edge_case].each do |persona|
    context "with #{persona} data" do
      let(:input) { FakeDataDSL.as(persona) { generate("UserInput") } }

      it "sanitizes input" do
        result = Sanitizer.clean(input)
        expect(result).not_to include("DROP TABLE")
        expect(result).not_to include("<script>")
      end
    end
  end
end

Persona-Specific Assertions

RSpec.describe "Order Processing" do
  context "as premium customer" do
    include_persona :premium_customer

    it "applies premium discount" do
      order = FakeDataDSL.generate("Order")
      processed = OrderProcessor.process(order, user)
      expect(processed.discount_rate).to eq(0.15)
    end
  end

  context "as free user" do
    include_persona :free_user

    it "applies no discount" do
      order = FakeDataDSL.generate("Order")
      processed = OrderProcessor.process(order, user)
      expect(processed.discount_rate).to eq(0)
    end
  end
end

API Reference

Personas.define

FakeDataDSL::Personas.define(name, options = {}, &block)

Parameters:

  • name - Symbol name for the persona
  • options[:extends] - Parent persona(s) to inherit from
  • block - Persona definition block

Persona DSL Methods

Inside a persona definition block:

FakeDataDSL::Personas.define(:example) do
  # Set generation mode
  mode :random  # :random, :edge, :invalid, :hostile, :mixed

  # Set locale for Faker
  locale :en  # or [:en, :de, :ja] for mixed

  # Set default seed
  seed 42

  # Add field overrides for a schema
  override "SchemaName", field: value, other_field: other_value

  # Add behaviors
  behavior :latency, min: 100, max: 500
  behavior :failure, probability: 0.05

  # Add description
  description "A persona representing premium customers"
end

FakeDataDSL.as

FakeDataDSL.as(persona_name, **options, &block)

Parameters:

  • persona_name - Symbol name of the persona
  • options - Override persona parameters
  • block - Block to execute with persona active

Returns: Result of the block

Personas.all

FakeDataDSL::Personas.all
# => Array of all defined personas

Personas.find

FakeDataDSL::Personas.find(:happy_path)
# => Persona instance

Best Practices

1. Name Personas by User Type

# Good: Descriptive user archetypes
:premium_customer
:trial_user
:enterprise_admin
:churned_customer

# Avoid: Technical descriptions
:random_data
:edge_mode_user
:test_data_1

2. Document Persona Purpose

FakeDataDSL::Personas.define(:fraud_suspect) do
  description <<~DESC
    A user account showing suspicious activity patterns:
    - Multiple failed payment attempts
    - Mismatched billing/shipping addresses
    - Recently created account with high-value orders
  DESC

  override "User",
    created_at: 1.day.ago,
    failed_payments: 3,
    billing_country: "US",
    shipping_country: "NG"
end

3. Keep Personas Focused

# Good: Single responsibility
FakeDataDSL::Personas.define(:international_user) do
  locale [:zh, :ar, :ru]
end

FakeDataDSL::Personas.define(:premium_user) do
  override "User", subscription: "premium"
end

# Use combinations
FakeDataDSL.as(:international_user, :premium_user) { ... }

4. Test Coverage with Personas

# Ensure critical paths are tested with multiple personas
CRITICAL_PERSONAS = [:happy_path, :edge_case, :hacker, :international]

RSpec.describe "Payment Processing" do
  CRITICAL_PERSONAS.each do |persona|
    it "handles #{persona} correctly" do
      # Test each persona
    end
  end
end

Troubleshooting

Persona Not Found

# Error: Unknown persona: :my_persona

# Fix: Define before use
FakeDataDSL::Personas.define(:my_persona) { ... }

# Or check available personas
FakeDataDSL::Personas.all.map(&:name)
# => [:happy_path, :edge_case, ...]

Overrides Not Applied

# Make sure override schema name matches exactly
FakeDataDSL::Personas.define(:test) do
  override "User", active: true     # ✓ Matches "User" schema
  override "user", active: true     # ✗ Case sensitive!
  override :User, active: true      # ✗ Symbol won't match string
end

Inheritance Conflicts

# Child overrides take precedence
FakeDataDSL::Personas.define(:parent) do
  override "User", status: "active"
end

FakeDataDSL::Personas.define(:child, extends: :parent) do
  override "User", status: "pending"  # This wins
end

See Also