sus 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/context/getting-started.md +352 -0
  4. data/context/index.yaml +9 -0
  5. data/context/mocking.md +100 -30
  6. data/context/{shared.md → shared-contexts.md} +29 -2
  7. data/lib/sus/assertions.rb +91 -18
  8. data/lib/sus/base.rb +13 -1
  9. data/lib/sus/be.rb +84 -0
  10. data/lib/sus/be_truthy.rb +16 -0
  11. data/lib/sus/be_within.rb +25 -0
  12. data/lib/sus/clock.rb +21 -0
  13. data/lib/sus/config.rb +58 -1
  14. data/lib/sus/context.rb +28 -5
  15. data/lib/sus/describe.rb +14 -0
  16. data/lib/sus/expect.rb +23 -0
  17. data/lib/sus/file.rb +38 -0
  18. data/lib/sus/filter.rb +21 -0
  19. data/lib/sus/fixtures/temporary_directory_context.rb +27 -0
  20. data/lib/sus/fixtures.rb +1 -0
  21. data/lib/sus/have/all.rb +8 -0
  22. data/lib/sus/have/any.rb +8 -0
  23. data/lib/sus/have.rb +42 -0
  24. data/lib/sus/have_duration.rb +16 -0
  25. data/lib/sus/identity.rb +44 -1
  26. data/lib/sus/integrations.rb +1 -0
  27. data/lib/sus/it.rb +33 -0
  28. data/lib/sus/it_behaves_like.rb +16 -0
  29. data/lib/sus/let.rb +3 -0
  30. data/lib/sus/mock.rb +39 -1
  31. data/lib/sus/output/backtrace.rb +31 -1
  32. data/lib/sus/output/bar.rb +17 -0
  33. data/lib/sus/output/buffered.rb +32 -1
  34. data/lib/sus/output/lines.rb +10 -0
  35. data/lib/sus/output/messages.rb +26 -3
  36. data/lib/sus/output/null.rb +16 -2
  37. data/lib/sus/output/progress.rb +29 -1
  38. data/lib/sus/output/status.rb +13 -0
  39. data/lib/sus/output/structured.rb +14 -1
  40. data/lib/sus/output/text.rb +33 -1
  41. data/lib/sus/output/xterm.rb +11 -1
  42. data/lib/sus/output.rb +9 -0
  43. data/lib/sus/raise_exception.rb +16 -0
  44. data/lib/sus/receive.rb +82 -0
  45. data/lib/sus/registry.rb +20 -1
  46. data/lib/sus/respond_to.rb +29 -2
  47. data/lib/sus/shared.rb +16 -0
  48. data/lib/sus/tree.rb +10 -0
  49. data/lib/sus/version.rb +2 -1
  50. data/lib/sus/with.rb +18 -0
  51. data/readme.md +8 -0
  52. data/releases.md +4 -0
  53. data.tar.gz.sig +0 -0
  54. metadata +3 -3
  55. metadata.gz.sig +0 -0
  56. data/context/usage.md +0 -380
@@ -1,16 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022, by Samuel Williams.
4
+ # Copyright, 2022-2025, by Samuel Williams.
5
5
 
6
6
  module Sus
7
+ # Represents a predicate that checks if an object responds to a method.
7
8
  class RespondTo
9
+ # Represents a constraint on method parameters.
8
10
  class WithParameters
9
- # @parameter [Array(Symbol)] List of method parameters in the expected order, must include at least all required parameters but can also list optional parameters.
11
+ # Initialize a new WithParameters constraint.
12
+ # @parameter parameters [Array(Symbol)] List of method parameters in the expected order, must include at least all required parameters but can also list optional parameters.
10
13
  def initialize(parameters)
11
14
  @parameters = parameters
12
15
  end
13
16
 
17
+ # Evaluate this constraint against method parameters.
18
+ # @parameter assertions [Assertions] The assertions instance to use.
19
+ # @parameter subject [Array] The method parameters to check.
14
20
  def call(assertions, subject)
15
21
  parameters = @parameters.dup
16
22
 
@@ -32,15 +38,23 @@ module Sus
32
38
  end
33
39
  end
34
40
 
41
+ # Represents a constraint on method keyword options.
35
42
  class WithOptions
43
+ # Initialize a new WithOptions constraint.
44
+ # @parameter options [Array(Symbol)] The option names that should be present.
36
45
  def initialize(options)
37
46
  @options = options
38
47
  end
39
48
 
49
+ # Print a representation of this constraint.
50
+ # @parameter output [Output] The output target.
40
51
  def print(output)
41
52
  output.write("with options ", :variable, @options.inspect)
42
53
  end
43
54
 
55
+ # Evaluate this constraint against method parameters.
56
+ # @parameter assertions [Assertions] The assertions instance to use.
57
+ # @parameter subject [Array] The method parameters to check.
44
58
  def call(assertions, subject)
45
59
  options = {}
46
60
  @options.each{|name| options[name] = nil}
@@ -57,21 +71,31 @@ module Sus
57
71
  end
58
72
  end
59
73
 
74
+ # Initialize a new RespondTo predicate.
75
+ # @parameter method [Symbol, String] The method name to check for.
60
76
  def initialize(method)
61
77
  @method = method
62
78
  @parameters = nil
63
79
  @options = nil
64
80
  end
65
81
 
82
+ # Specify that the method should have specific keyword options.
83
+ # @parameter options [Array(Symbol)] The option names that should be present.
84
+ # @returns [RespondTo] Returns self for method chaining.
66
85
  def with_options(*options)
67
86
  @options = WithOptions.new(options)
68
87
  return self
69
88
  end
70
89
 
90
+ # Print a representation of this predicate.
91
+ # @parameter output [Output] The output target.
71
92
  def print(output)
72
93
  output.write("respond to ", :variable, @method.to_s, :reset)
73
94
  end
74
95
 
96
+ # Evaluate this predicate against a subject.
97
+ # @parameter assertions [Assertions] The assertions instance to use.
98
+ # @parameter subject [Object] The subject to evaluate.
75
99
  def call(assertions, subject)
76
100
  assertions.nested(self) do |assertions|
77
101
  condition = subject.respond_to?(@method)
@@ -87,6 +111,9 @@ module Sus
87
111
  end
88
112
 
89
113
  class Base
114
+ # Create a predicate that checks if the subject responds to a method.
115
+ # @parameter method [Symbol, String] The method name to check for.
116
+ # @returns [RespondTo] A new RespondTo predicate.
90
117
  def respond_to(method)
91
118
  RespondTo.new(method)
92
119
  end
data/lib/sus/shared.rb CHANGED
@@ -6,10 +6,18 @@
6
6
  require_relative "context"
7
7
 
8
8
  module Sus
9
+ # Represents a shared test context that can be reused across multiple test files.
9
10
  module Shared
11
+ # @attribute [String] The name of the shared context.
10
12
  attr_accessor :name
13
+
14
+ # @attribute [Proc] The block containing the shared test code.
11
15
  attr_accessor :block
12
16
 
17
+ # Build a new Shared context.
18
+ # @parameter name [String] The name of the shared context.
19
+ # @parameter block [Proc] The block containing the shared test code.
20
+ # @returns [Module] A new Shared module.
13
21
  def self.build(name, block)
14
22
  base = Module.new
15
23
  base.extend(Shared)
@@ -19,15 +27,23 @@ module Sus
19
27
  return base
20
28
  end
21
29
 
30
+ # Called when this module is included in a test class.
31
+ # @parameter base [Class] The class including this module.
22
32
  def included(base)
23
33
  base.class_exec(&self.block)
24
34
  end
25
35
 
36
+ # Called when this module is prepended to a test class.
37
+ # @parameter base [Class] The class prepending this module.
26
38
  def prepended(base)
27
39
  base.class_exec(&self.block)
28
40
  end
29
41
  end
30
42
 
43
+ # Create a new shared test context.
44
+ # @parameter name [String] The name of the shared context.
45
+ # @yields {...} The block containing the shared test code.
46
+ # @returns [Shared] A new Shared module.
31
47
  def self.Shared(name, &block)
32
48
  Shared.build(name, block)
33
49
  end
data/lib/sus/tree.rb CHANGED
@@ -4,11 +4,18 @@
4
4
  # Copyright, 2023, by Samuel Williams.
5
5
 
6
6
  module Sus
7
+ # Represents a tree structure of test contexts.
7
8
  class Tree
9
+ # Initialize a new Tree.
10
+ # @parameter context [Object] The root context.
8
11
  def initialize(context)
9
12
  @context = context
10
13
  end
11
14
 
15
+ # Traverse the tree, yielding each context.
16
+ # @parameter current [Object] The current context (defaults to root).
17
+ # @yields {|context| ...} Each context in the tree.
18
+ # @returns [Hash] A hash representation of the tree.
12
19
  def traverse(current = @context, &block)
13
20
  node = {}
14
21
 
@@ -23,6 +30,9 @@ module Sus
23
30
  return node
24
31
  end
25
32
 
33
+ # Convert the tree to JSON.
34
+ # @parameter options [Hash, nil] Options to pass to JSON.generate.
35
+ # @returns [String] A JSON representation of the tree.
26
36
  def to_json(options = nil)
27
37
  traverse do |context|
28
38
  [context.identity.to_s, context.description.to_s, context.leaf?]
data/lib/sus/version.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2021-2025, by Samuel Williams.
5
5
 
6
+ # @namespace
6
7
  module Sus
7
- VERSION = "0.34.0"
8
+ VERSION = "0.35.0"
8
9
  end
data/lib/sus/with.rb CHANGED
@@ -6,12 +6,23 @@
6
6
  require_relative "context"
7
7
 
8
8
  module Sus
9
+ # Represents a test context with specific conditions or variables.
9
10
  module With
10
11
  extend Context
11
12
 
13
+ # @attribute [String] The subject description of this context.
12
14
  attr_accessor :subject
15
+
16
+ # @attribute [Hash] The variables available in this context.
13
17
  attr_accessor :variables
14
18
 
19
+ # Build a new with block class.
20
+ # @parameter parent [Class] The parent context class.
21
+ # @parameter subject [String] The subject description.
22
+ # @parameter variables [Hash] Variables to make available in the context.
23
+ # @parameter unique [Boolean] Whether the identity should be unique.
24
+ # @yields {...} Optional block containing nested tests.
25
+ # @returns [Class] A new with block class.
15
26
  def self.build(parent, subject, variables, unique: true, &block)
16
27
  base = Class.new(parent)
17
28
  base.singleton_class.prepend(With)
@@ -36,6 +47,8 @@ module Sus
36
47
  return base
37
48
  end
38
49
 
50
+ # Print a representation of this with block.
51
+ # @parameter output [Output] The output target.
39
52
  def print(output)
40
53
  self.superclass.print(output)
41
54
 
@@ -47,6 +60,11 @@ module Sus
47
60
  end
48
61
 
49
62
  module Context
63
+ # Define a new test context with specific conditions or variables.
64
+ # @parameter subject [String | Nil] Optional subject description. If nil, uses variables.inspect.
65
+ # @parameter unique [Boolean] Whether the identity should be unique.
66
+ # @parameter variables [Hash] Variables to make available in the context.
67
+ # @yields {...} Optional block containing nested tests.
50
68
  def with(subject = nil, unique: true, **variables, &block)
51
69
  subject ||= variables.inspect
52
70
 
data/readme.md CHANGED
@@ -25,10 +25,18 @@ Please see the [project documentation](https://socketry.github.io/sus/) for more
25
25
 
26
26
  - [Getting Started](https://socketry.github.io/sus/guides/getting-started/index) - This guide explains how to use the `sus` gem to write tests for your Ruby projects.
27
27
 
28
+ - [Mocking](https://socketry.github.io/sus/guides/mocking/index) - This guide explains how to use mocking in sus to isolate dependencies and verify interactions in your tests.
29
+
30
+ - [Shared Test Behaviors and Fixtures](https://socketry.github.io/sus/guides/shared-contexts/index) - This guide explains how to use shared test contexts and fixtures in sus to reduce duplication and ensure consistent test behavior across your test suite.
31
+
28
32
  ## Releases
29
33
 
30
34
  Please see the [project releases](https://socketry.github.io/sus/releases/index) for all releases.
31
35
 
36
+ ### v0.35.0
37
+
38
+ - Add `Sus::Fixtures::TemporaryDirectoryContext`.
39
+
32
40
  ### v0.34.0
33
41
 
34
42
  - Allow `expect(...).to receive(...)` to accept one or more calls (at least once).
data/releases.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Releases
2
2
 
3
+ ## v0.35.0
4
+
5
+ - Add `Sus::Fixtures::TemporaryDirectoryContext`.
6
+
3
7
  ## v0.34.0
4
8
 
5
9
  - Allow `expect(...).to receive(...)` to accept one or more calls (at least once).
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.34.0
4
+ version: 0.35.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -54,8 +54,7 @@ files:
54
54
  - context/getting-started.md
55
55
  - context/index.yaml
56
56
  - context/mocking.md
57
- - context/shared.md
58
- - context/usage.md
57
+ - context/shared-contexts.md
59
58
  - lib/sus.rb
60
59
  - lib/sus/assertions.rb
61
60
  - lib/sus/base.rb
@@ -70,6 +69,7 @@ files:
70
69
  - lib/sus/file.rb
71
70
  - lib/sus/filter.rb
72
71
  - lib/sus/fixtures.rb
72
+ - lib/sus/fixtures/temporary_directory_context.rb
73
73
  - lib/sus/have.rb
74
74
  - lib/sus/have/all.rb
75
75
  - lib/sus/have/any.rb
metadata.gz.sig CHANGED
Binary file
data/context/usage.md DELETED
@@ -1,380 +0,0 @@
1
- # Using Sus Testing Framework
2
-
3
- ## Overview
4
-
5
- Sus is a modern Ruby testing framework that provides a clean, BDD-style syntax for writing tests. It's designed to be fast, simple, and expressive.
6
-
7
- ## Basic Structure
8
-
9
- Here is an example structure for testing with Sus - the actual structure may vary based on your gem's organization, but aside from the `lib/` directory, sus expects the following structure:
10
-
11
- ```
12
- my-gem/
13
- ├── config/
14
- │ └── sus.rb # Sus configuration file
15
- ├── lib/
16
- │ ├── my_gem.rb
17
- │ └── my_gem/
18
- │ └── my_thing.rb
19
- ├── fixtures/
20
- │ └── my_gem/
21
- │ └── a_thing.rb # Provides MyGem::AThing shared context
22
- └── test/
23
- ├── my_gem.rb # Tests MyGem
24
- └── my_gem/
25
- └── my_thing.rb # Tests MyGem::MyThing
26
- ```
27
-
28
- ### Configuration File
29
-
30
- Create `config/sus.rb`:
31
-
32
- ```ruby
33
- # frozen_string_literal: true
34
-
35
- # Use the covered gem for test coverage reporting:
36
- require 'covered/sus'
37
- include Covered::Sus
38
-
39
- def before_tests(assertions, output: self.output)
40
- # Starts the clock and sets up the test environment:
41
- super
42
- end
43
-
44
- def after_tests(assertions, output: self.output)
45
- # Stops the clock and prints the test results:
46
- super
47
- end
48
- ```
49
-
50
- ### Fixtures Files
51
-
52
- `fixtures/` gets added to the `$LOAD_PATH` automatically, so you can require files from there without needing to specify the full path.
53
-
54
- ### Test Files
55
-
56
- Sus runs all Ruby files in the `test/` directory by default. But you can also create tests in any file, and run them with the `sus my_tests.rb` command.
57
-
58
- ## Basic Syntax
59
-
60
- ```ruby
61
- # frozen_string_literal: true
62
-
63
- describe MyThing do
64
- let(:my_thing) {subject.new}
65
-
66
- with "#my_method" do
67
- it "does something" do
68
- expect(my_thing.my_method).to be == 42
69
- end
70
- end
71
- end
72
- ```
73
-
74
- ### `describe` - Test Groups
75
-
76
- Use `describe` to group related tests:
77
-
78
- ```ruby
79
- describe MyThing do
80
- # The subject will be whatever is described:
81
- let(:my_thing) {subject.new}
82
- end
83
- ```
84
-
85
- ### `it` - Individual Tests
86
-
87
- Use `it` to define individual test cases:
88
-
89
- ```ruby
90
- it "returns the expected value" do
91
- expect(result).to be == "expected"
92
- end
93
- ```
94
-
95
- You can use `it` blocks at the top level or within `describe` or `with` blocks.
96
-
97
- ### `with` - Context Blocks
98
-
99
- Use `with` to create context-specific test groups:
100
-
101
- ```ruby
102
- with "valid input" do
103
- let(:input) {"valid input"}
104
- it "succeeds" do
105
- expect{my_thing.process(input)}.not.to raise_exception
106
- end
107
- end
108
-
109
- # Non-lazy state can be provided as keyword arguments:
110
- with "invalid input", input: nil do
111
- it "raises an error" do
112
- expect{my_thing.process(input)}.to raise_exception(ArgumentError)
113
- end
114
- end
115
- ```
116
-
117
- When testing methods, use `with` to specify the method being tested:
118
-
119
- ```ruby
120
- with "#my_method" do
121
- it "results a value" do
122
- expect(my_thing.method).to be == 42
123
- end
124
- end
125
-
126
- with ".my_class_method" do
127
- it "returns a value" do
128
- expect(MyThing.class_method).to be == "class value"
129
- end
130
- end
131
- ```
132
-
133
- ### `let` - Lazy Variables
134
-
135
- Use `let` to define variables that are evaluated when first accessed:
136
-
137
- ```ruby
138
- let(:helper) {subject.new}
139
- let(:test_data) {"test value"}
140
-
141
- it "uses the helper" do
142
- expect(helper.process(test_data)).to be_truthy
143
- end
144
- ```
145
-
146
- ### `before` and `after` - Setup/Teardown
147
-
148
- Use `before` and `after` for setup and teardown logic:
149
-
150
- ```ruby
151
- before do
152
- # Setup logic.
153
- end
154
-
155
- after do
156
- # Cleanup logic.
157
- end
158
- ```
159
-
160
- Error handling in `after` allows you to perform cleanup even if the test fails with an exception (not a test failure).
161
-
162
- ```ruby
163
- after do |error = nil|
164
- if error
165
- # The state of the test is unknown, so you may want to forcefully kill processes or clean up resources.
166
- Process.kill(:KILL, @child_pid)
167
- else
168
- # Normal cleanup logic.
169
- Process.kill(:TERM, @child_pid)
170
- end
171
-
172
- Process.waitpid(@child_pid)
173
- end
174
- ```
175
-
176
- ### `around` - Setup/Teardown
177
-
178
- Use `around` for setup and teardown logic:
179
-
180
- ```ruby
181
- around do |&block|
182
- # Setup logic.
183
- super() do
184
- # Run the test.
185
- block.call
186
- end
187
- ensure
188
- # Cleanup logic.
189
- end
190
- ```
191
-
192
- Invoking `super()` calls any parent `around` block, allowing you to chain setup and teardown logic.
193
-
194
- ## Assertions
195
-
196
- ### Basic Assertions
197
-
198
- ```ruby
199
- expect(value).to be == expected
200
- exepct(value).to be >= 10
201
- expect(value).to be <= 100
202
- expect(value).to be > 0
203
- expect(value).to be < 1000
204
- expect(value).to be_truthy
205
- expect(value).to be_falsey
206
- expect(value).to be_nil
207
- expect(value).to be_equal(another_value)
208
- expect(value).to be_a(Class)
209
- ```
210
-
211
- ### Strings
212
-
213
- ```ruby
214
- expect(string).to be(:start_with?, "prefix")
215
- expect(string).to be(:end_with?, "suffix")
216
- expect(string).to be(:match?, /pattern/)
217
- expect(string).to be(:include?, "substring")
218
- ```
219
-
220
- ### Ranges and Tolerance
221
-
222
- ```ruby
223
- expect(value).to be_within(0.1).of(5.0)
224
- expect(value).to be_within(5).percent_of(100)
225
- ```
226
-
227
- ### Method Calls
228
-
229
- To call methods on the expected object:
230
-
231
- ```ruby
232
- expect(array).to be(:include?, "value")
233
- expect(string).to be(:start_with?, "prefix")
234
- expect(object).to be(:respond_to?, :method_name)
235
- ```
236
-
237
- ### Collection Assertions
238
-
239
- ```ruby
240
- expect(array).to have_attributes(length: be == 1)
241
- expect(array).to have_value(be > 1)
242
-
243
- expect(hash).to have_keys(:key1, "key2")
244
- expect(hash).to have_keys(key1: be == 1, "key2" => be == 2)
245
- ```
246
-
247
- ### Attribute Testing
248
-
249
- ```ruby
250
- expect(user).to have_attributes(
251
- name: be == "John",
252
- age: be >= 18,
253
- email: be(:include?, "@")
254
- )
255
- ```
256
-
257
- ### Exception Assertions
258
-
259
- ```ruby
260
- expect do
261
- risky_operation
262
- end.to raise_exception(RuntimeError, message: be =~ /expected error message/)
263
- ```
264
-
265
- ## Combining Predicates
266
-
267
- Predicates can be nested.
268
-
269
- ```ruby
270
- expect(user).to have_attributes(
271
- name: have_attributes(
272
- first: be == "John",
273
- last: be == "Doe"
274
- ),
275
- comments: have_value(be =~ /test comment/),
276
- created_at: be_within(1.minute).of(Time.now)
277
- )
278
- ```
279
-
280
- ### Logical Combinations
281
-
282
- ```ruby
283
- expect(value).to (be > 10).and(be < 20)
284
- expect(value).to be_a(String).or(be_a(Symbol), be_a(Integer))
285
- ```
286
-
287
- ### Custom Predicates
288
-
289
- You can create custom predicates for more complex assertions:
290
-
291
- ```ruby
292
- def be_small_prime
293
- (be == 2).or(be == 3, be == 5, be == 7)
294
- end
295
- ```
296
-
297
- ## Block Expectations
298
-
299
- ### Testing Blocks
300
-
301
- ```ruby
302
- expect{operation}.to raise_exception(Error)
303
- expect{operation}.to have_duration(be < 1.0)
304
- ```
305
-
306
- ### Performance Testing
307
-
308
- You should generally avoid testing performance in unit tests, as it will be highly unstable and dependent on the environment. However, if you need to test performance, you can use:
309
-
310
- ```ruby
311
- expect{slow_operation}.to have_duration(be < 2.0)
312
- expect{fast_operation}.to have_duration(be < 0.1)
313
- ```
314
-
315
- - For less unsable performance tests, you can use the `sus-fixtures-time` gem which tries to compensate for the environment by measuring execution time.
316
-
317
- - For benchmarking, you can use the `sus-fixtures-benchmark` gem which measures a block of code multiple times and reports the execution time.
318
-
319
- ## File Operations
320
-
321
- ### Temporary Directories
322
-
323
- Use `Dir.mktmpdir` for isolated test environments:
324
-
325
- ```ruby
326
- around do |block|
327
- Dir.mktmpdir do |root|
328
- @root = root
329
- block.call
330
- end
331
- end
332
-
333
- let(:test_path) {File.join(@root, "test.txt")}
334
-
335
- it "can create a file" do
336
- File.write(test_path, "content")
337
- expect(File).to be(:exist?, test_path)
338
- end
339
- ```
340
-
341
- ## Test Output
342
-
343
- In general, tests should not produce output unless there is an error or failure.
344
-
345
- ### Informational Output
346
-
347
- You can use `inform` to print informational messages during tests:
348
-
349
- ```ruby
350
- it "logs an informational message" do
351
- rate = copy_data(source, destination)
352
- inform "Copied data at #{rate}MB/s"
353
- expect(rate).to be > 0
354
- end
355
- ```
356
-
357
- This can be useful for debugging or providing context during test runs.
358
-
359
- ### Console Output
360
-
361
- The `sus-fixtures-console` gem provides a way to surpress and capture console output during tests. If you are using code which generates console output, you can use this gem to capture it and assert on it.
362
-
363
- ## Running Tests
364
-
365
- ```bash
366
- # Run all tests
367
- bundle exec sus
368
-
369
- # Run specific test file
370
- bundle exec sus test/specific_test.rb
371
- ```
372
-
373
- ## Best Practices
374
-
375
- 1. **Use real objects** instead of mocks when possible.
376
- 2. **Dependency injection** for testability.
377
- 3. **Isolatae mutable state** using temporary directories.
378
- 4. **Clear test descriptions** that explain the behavior.
379
- 5. **Group tests** with `describe` (classes) and `with` for better organization.
380
- 6. **Keep tests simple** and focused on one behavior.