Module: Yes::Core::TestSupport::Aggregate::CommandTestDsl

Defined in:
lib/yes/core/test_support/aggregate/command_test_dsl.rb

Overview

DSL for writing concise aggregate command specs.

Provides ‘command`, `success`, `invalid`, `no_change`, and `setup` methods that generate RSpec describe/context blocks with appropriate shared examples.

Examples:

RSpec.describe MyContext::MyAggregate::Aggregate, type: :aggregate do
  command 'do_something' do
    let(:command_data) { { name: 'test' } }
    let(:success_attributes) { { name: 'test' } }

    success
    invalid 'when precondition not met'
    no_change
  end
end

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.expected_event_prefix(aggregate_class, draft:) ⇒ String

Returns the event-type aggregate prefix that the runtime publishes for a given aggregate and draft flag. Mirrors ‘CommandUtils#aggregate_name_with_draft_suffix` so DSL-generated `expected_event_type` values match the runtime-published event types — including the case where `draftable changes_read_model:` was set explicitly, which makes the camelized read-model name the event-type prefix instead of the generic `<Aggregate>Draft`.

Parameters:

  • aggregate_class (Class)

    The aggregate class under test

  • draft (Boolean)

    Whether the test exercises a draft command

Returns:

  • (String)

    The event-type aggregate prefix



35
36
37
38
39
40
41
42
43
44
# File 'lib/yes/core/test_support/aggregate/command_test_dsl.rb', line 35

def self.expected_event_prefix(aggregate_class, draft:)
  return aggregate_class.aggregate unless draft

  if aggregate_class.respond_to?(:_changes_read_model_explicit) &&
     aggregate_class._changes_read_model_explicit
    aggregate_class.changes_read_model_name.camelize
  else
    "#{aggregate_class.aggregate}Draft"
  end
end

Instance Method Details

#command(command_name, *options) { ... } ⇒ Object

Defines a test block for a command

Parameters:

  • command_name (String, Symbol)

    the name of the command to test

  • options (Array<Hash>)

    additional options (e.g., ‘draft: true`, VCR cassettes)

Yields:

  • block for configuring test cases with success/invalid/no_change



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/yes/core/test_support/aggregate/command_test_dsl.rb', line 51

def command(command_name, *options, &block)
  describe command_name.to_s, *options do
    let(:draft) { options.first&.dig(:draft) }

    let(:aggregate) { described_class.new(draft:) } unless method_defined?(:aggregate)

    subject { aggregate.public_send(command, command_data, guards: !draft) }

    let(:command) { command_name.to_sym }
    let(:aggregate_class) { aggregate.class }
    let(:command_data_with_id) do
      { "#{aggregate_class.aggregate.underscore}_id" => aggregate.id }.merge(command_data)
    end
    let(:command_data) { {} }
    let(:expected_event_type) do
      prefix = CommandTestDsl.expected_event_prefix(aggregate_class, draft:)
      "#{aggregate_class.context}::#{prefix}#{aggregate_class.commands[command].event_name.to_s.classify}"
    end
    let(:expected_event_data) { command_data_with_id }
    let(:expected_event_metadata) { nil }
    let(:success_attributes) { command_data.without(:locale) } unless method_defined?(:success_attributes)

    class_eval(&block) if block_given?
  end
end

#command_group(group_name, *options) { ... } ⇒ Object

Defines a test block for a command group, mirroring #command.

Parameters:

  • group_name (String, Symbol)

    the command_group name

  • options (Array<Hash>)

    additional options (e.g., ‘draft: true`)

Yields:

  • block for configuring test cases (success, invalid, no_change)



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/yes/core/test_support/aggregate/command_test_dsl.rb', line 130

def command_group(group_name, *options, &block)
  describe group_name.to_s, *options do
    let(:draft) { options.first&.dig(:draft) }
    let(:aggregate) { described_class.new(draft:) } unless method_defined?(:aggregate)

    subject { aggregate.public_send(group, command_data) }

    let(:group) { group_name.to_sym }
    let(:aggregate_class) { aggregate.class }
    let(:command_data) { {} }
    let(:expected_event_types) do
      prefix = CommandTestDsl.expected_event_prefix(aggregate_class, draft:)
      aggregate_class.command_groups[group].sub_command_names.map do |sub_name|
        sub_event_name = aggregate_class.commands[sub_name].event_name.to_s.classify
        "#{aggregate_class.context}::#{prefix}#{sub_event_name}"
      end
    end
    let(:success_attributes) { command_data.without(:locale) } unless method_defined?(:success_attributes)

    class_eval(&block) if block_given?
  end
end

#invalid(description, options = {}) { ... } ⇒ Object

Defines a test case for an invalid transition

Parameters:

  • description (String)

    description of the invalid scenario

  • options (Hash) (defaults to: {})

    additional options

Yields:

  • optional block for additional setup



110
111
112
113
114
115
116
# File 'lib/yes/core/test_support/aggregate/command_test_dsl.rb', line 110

def invalid(description, options = {}, &block)
  context "when #{description}", options do
    instance_eval(&block) if block_given?

    it_behaves_like 'invalid transition'
  end
end

#invalid_group(description, options = {}, &block) ⇒ Object

Defines an invalid-transition test for a command group.



162
163
164
165
166
167
# File 'lib/yes/core/test_support/aggregate/command_test_dsl.rb', line 162

def invalid_group(description, options = {}, &block)
  context "when #{description}", options do
    instance_eval(&block) if block_given?
    it_behaves_like 'invalid command group transition'
  end
end

#no_change(description = 'when command causes no change', options = {}) { ... } ⇒ Object

Defines a test case for a command that causes no state change

Parameters:

  • description (String) (defaults to: 'when command causes no change')

    optional description

  • options (Hash) (defaults to: {})

    additional options

Yields:

  • optional block for additional setup



95
96
97
98
99
100
101
102
103
# File 'lib/yes/core/test_support/aggregate/command_test_dsl.rb', line 95

def no_change(description = 'when command causes no change', options = {}, &block)
  context description.to_s, options do
    instance_eval(&block) if block_given?

    before { aggregate.public_send(command, command_data) }

    it_behaves_like 'no change transition'
  end
end

#no_change_group(description = 'when command group causes no change', options = {}, &block) ⇒ Object

Defines a no-change test for a command group.



170
171
172
173
174
175
176
# File 'lib/yes/core/test_support/aggregate/command_test_dsl.rb', line 170

def no_change_group(description = 'when command group causes no change', options = {}, &block)
  context description.to_s, options do
    instance_eval(&block) if block_given?
    before { aggregate.public_send(group, command_data) }
    it_behaves_like 'no change command group transition'
  end
end

#setup { ... } ⇒ Object

Alias for ‘before` — used for readable aggregate setup within command blocks

Yields:

  • block for setup actions



121
122
123
# File 'lib/yes/core/test_support/aggregate/command_test_dsl.rb', line 121

def setup(&)
  before(&)
end

#success(description = 'when successfully executing command', options = {}) { ... } ⇒ Object

Defines a test case for a successful command execution

Parameters:

  • description (String) (defaults to: 'when successfully executing command')

    optional description

  • options (Hash) (defaults to: {})

    additional options (e.g., VCR cassettes)

Yields:

  • optional block for additional setup or custom assertions



82
83
84
85
86
87
88
# File 'lib/yes/core/test_support/aggregate/command_test_dsl.rb', line 82

def success(description = 'when successfully executing command', options = {}, &block)
  context description, options do
    instance_eval(&block) if block_given?

    it_behaves_like 'successful command'
  end
end

#success_group(description = 'when successfully executing command group', options = {}, &block) ⇒ Object

Defines a successful test for a command group.



154
155
156
157
158
159
# File 'lib/yes/core/test_support/aggregate/command_test_dsl.rb', line 154

def success_group(description = 'when successfully executing command group', options = {}, &block)
  context description, options do
    instance_eval(&block) if block_given?
    it_behaves_like 'successful command group'
  end
end