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, = {}, &block)
Parameters:
name- Symbol name for the personaoptions[:extends]- Parent persona(s) to inherit fromblock- 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, **, &block)
Parameters:
persona_name- Symbol name of the personaoptions- Override persona parametersblock- 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