Class: Servactory::TestKit::Rspec::Helpers::ServiceMockBuilder

Inherits:
Object
  • Object
show all
Includes:
Concerns::ErrorMessages, Concerns::ServiceClassValidation
Defined in:
lib/servactory/test_kit/rspec/helpers/service_mock_builder.rb

Overview

Fluent builder for configuring Servactory service mocks in RSpec tests.

## Purpose

ServiceMockBuilder provides a fluent API for stubbing Servactory service calls in tests. It handles both success and failure scenarios, output configuration, input argument matching, and sequential call responses.

## Usage

Basic success mock:

“‘ruby allow_service(MyService)

.succeeds(result: "value")

“‘

Failure mock:

“‘ruby allow_service(MyService)

.fails(type: :base, message: "Error")

“‘

Failure mock with custom exception class:

“‘ruby allow_service(MyService)

.fails(CustomException, type: :base, message: "Error")

“‘

Sequential returns (first call succeeds, second fails):

“‘ruby allow_service(MyService)

.succeeds(count: 1)
.then_succeeds(count: 2)
.then_fails(type: :base, message: "Error")

“‘

With input matching:

“‘ruby allow_service(MyService)

.with(user_id: 123)
.succeeds(user: user)

# Or order doesn’t matter: allow_service(MyService)

.succeeds(user: user)
.with(user_id: 123)

“‘

## Features

  • **Fluent API** - chainable methods for readable test setup

  • Success/Failure - configure expected result type in one method

  • **Exception Handling** - auto-creates exceptions with type, message, meta

  • **Input Matching** - match specific service inputs with ‘.with()`

  • **Sequential Responses** - different results for consecutive calls

  • **Automatic Validation** - validates inputs and outputs against service definition

## Architecture

Works with:

  • ServiceMockConfig - holds mock configuration state

  • MockExecutor - executes the actual RSpec stubbing

  • OutputValidator - validates outputs match service definition

  • InputValidator - validates inputs match service definition

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(service_class, method_type:, rspec_context:) ⇒ ServiceMockBuilder

Creates a new service mock builder.

Parameters:

  • service_class (Class)

    The Servactory service class to mock

  • method_type (Symbol)

    The method to stub (:call or :call!)

  • rspec_context (Object)

    The RSpec example context for stubbing



91
92
93
94
95
96
97
98
99
100
# File 'lib/servactory/test_kit/rspec/helpers/service_mock_builder.rb', line 91

def initialize(service_class, method_type:, rspec_context:)
  validate_service_class!(service_class)

  @service_class = service_class
  @rspec_context = rspec_context
  @config = ServiceMockConfig.new(service_class:)
  @config.method_type = method_type
  @sequential_configs = []
  @executed = false
end

Instance Attribute Details

#configServiceMockConfig (readonly)

Returns Current mock configuration.

Returns:



84
85
86
# File 'lib/servactory/test_kit/rspec/helpers/service_mock_builder.rb', line 84

def config
  @config
end

#service_classClass (readonly)

Returns The Servactory service class being mocked.

Returns:

  • (Class)

    The Servactory service class being mocked



81
82
83
# File 'lib/servactory/test_kit/rspec/helpers/service_mock_builder.rb', line 81

def service_class
  @service_class
end

Instance Method Details

#fails(exception_class = nil, type: :base, message:, meta: nil) ⇒ ServiceMockBuilder

Configures the mock to return a failure result with exception.

Examples:

Minimal failure

allow_service(PaymentService).fails(message: "Card declined")

With type

allow_service(PaymentService)
  .fails(type: :payment_declined, message: "Insufficient funds")

With custom exception class

allow_service(PaymentService)
  .fails(CustomException, type: :error, message: "Error")

Parameters:

  • exception_class (Class, nil) (defaults to: nil)

    Exception class (default: service config.failure_class)

  • type (Symbol) (defaults to: :base)

    Error type (default: :base)

  • message (String)

    Error message (required)

  • meta (Object, nil) (defaults to: nil)

    Optional metadata for the exception

Returns:

Raises:

  • (ArgumentError)

    if called after then_succeeds/then_fails



154
155
156
157
158
159
160
161
162
# File 'lib/servactory/test_kit/rspec/helpers/service_mock_builder.rb', line 154

def fails(exception_class = nil, type: :base, message:, meta: nil) # rubocop:disable Style/KeywordParametersOrder
  validate_not_in_sequential_mode!(:fails)
  validate_result_type_not_switched!(:fails)

  @config.result_type = :failure
  @config.exception = build_exception(exception_class, type:, message:, meta:)
  execute_or_re_execute_mock
  self
end

#succeeds(outputs_hash = {}) ⇒ ServiceMockBuilder

Configures the mock to return a successful result with outputs.

Outputs are automatically validated against service definition.

Examples:

Basic success

allow_service(PaymentService).succeeds(status: :completed)

With input matching

allow_service(PaymentService)
  .succeeds(transaction_id: "txn_123")
  .with(amount: 100)

Parameters:

  • outputs_hash (Hash{Symbol => Object}) (defaults to: {})

    Output name-value pairs

Returns:

Raises:



123
124
125
126
127
128
129
130
131
132
# File 'lib/servactory/test_kit/rspec/helpers/service_mock_builder.rb', line 123

def succeeds(outputs_hash = {})
  validate_not_in_sequential_mode!(:succeeds)
  validate_result_type_not_switched!(:succeeds)

  validate_outputs!(outputs_hash)
  @config.result_type = :success
  @config.outputs = outputs_hash
  execute_or_re_execute_mock
  self
end

#then_fails(exception_class = nil, type: :base, message:, meta: nil) ⇒ ServiceMockBuilder

Adds a failure result for sequential call handling.

Use for testing code that calls the same service multiple times.

Examples:

Success then failure

allow_service(RetryService)
  .succeeds(status: :pending)
  .then_fails(type: :timeout, message: "Request timed out")

Parameters:

  • exception_class (Class, nil) (defaults to: nil)

    Exception class (default: service config.failure_class)

  • type (Symbol) (defaults to: :base)

    Error type (default: :base)

  • message (String)

    Error message (required)

  • meta (Object, nil) (defaults to: nil)

    Optional metadata for the exception

Returns:

Raises:

  • (ArgumentError)

    if called without first calling succeeds/fails



239
240
241
242
243
244
245
246
247
248
249
# File 'lib/servactory/test_kit/rspec/helpers/service_mock_builder.rb', line 239

def then_fails(exception_class = nil, type: :base, message:, meta: nil) # rubocop:disable Style/KeywordParametersOrder
  validate_result_type_defined!(:then_fails)

  finalize_current_to_sequence
  @config = ServiceMockConfig.new(service_class:)
  @config.result_type = :failure
  @config.exception = build_exception(exception_class, type:, message:, meta:)
  @config.method_type = @sequential_configs.last&.method_type || :call
  execute_sequential_mock
  self
end

#then_succeeds(outputs_hash = {}) ⇒ ServiceMockBuilder

Adds a successful result for sequential call handling.

Use for testing code that calls the same service multiple times. Outputs are automatically validated against service definition.

Examples:

Multiple successes

allow_service(RetryService)
  .succeeds(status: :pending)
  .then_succeeds(status: :completed)

Parameters:

  • outputs_hash (Hash{Symbol => Object}) (defaults to: {})

    Output name-value pairs

Returns:

Raises:



210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/servactory/test_kit/rspec/helpers/service_mock_builder.rb', line 210

def then_succeeds(outputs_hash = {})
  validate_result_type_defined!(:then_succeeds)

  validate_outputs!(outputs_hash)
  finalize_current_to_sequence
  @config = ServiceMockConfig.new(service_class:)
  @config.result_type = :success
  @config.outputs = outputs_hash
  @config.method_type = @sequential_configs.last&.method_type || :call
  execute_sequential_mock
  self
end

#with(inputs_hash_or_matcher) ⇒ ServiceMockBuilder

Configures input matching for the mock.

Can be called at any position in the chain (before/after succeeds/fails, or after then_* methods). Applies to the entire mock chain.

Inputs are automatically validated against service definition.

Examples:

Exact match

allow_service(S).with(amount: 100).succeeds(result: :ok)

With matchers

allow_service(S).with(including(amount: 100)).succeeds(result: :ok)

Any position in chain

allow_service(S).succeeds(result: :ok).with(amount: 100)

Parameters:

  • inputs_hash_or_matcher (Hash, Object)

    Service inputs to match or RSpec matcher

Returns:

Raises:



184
185
186
187
188
189
# File 'lib/servactory/test_kit/rspec/helpers/service_mock_builder.rb', line 184

def with(inputs_hash_or_matcher)
  validate_inputs!(inputs_hash_or_matcher)
  @config.argument_matcher = inputs_hash_or_matcher
  re_execute_mock if @executed
  self
end