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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/context/getting-started.md +352 -0
- data/context/index.yaml +9 -0
- data/context/mocking.md +100 -30
- data/context/{shared.md → shared-contexts.md} +29 -2
- data/lib/sus/assertions.rb +91 -18
- data/lib/sus/base.rb +13 -1
- data/lib/sus/be.rb +84 -0
- data/lib/sus/be_truthy.rb +16 -0
- data/lib/sus/be_within.rb +25 -0
- data/lib/sus/clock.rb +21 -0
- data/lib/sus/config.rb +58 -1
- data/lib/sus/context.rb +28 -5
- data/lib/sus/describe.rb +14 -0
- data/lib/sus/expect.rb +23 -0
- data/lib/sus/file.rb +38 -0
- data/lib/sus/filter.rb +21 -0
- data/lib/sus/fixtures/temporary_directory_context.rb +27 -0
- data/lib/sus/fixtures.rb +1 -0
- data/lib/sus/have/all.rb +8 -0
- data/lib/sus/have/any.rb +8 -0
- data/lib/sus/have.rb +42 -0
- data/lib/sus/have_duration.rb +16 -0
- data/lib/sus/identity.rb +44 -1
- data/lib/sus/integrations.rb +1 -0
- data/lib/sus/it.rb +33 -0
- data/lib/sus/it_behaves_like.rb +16 -0
- data/lib/sus/let.rb +3 -0
- data/lib/sus/mock.rb +39 -1
- data/lib/sus/output/backtrace.rb +31 -1
- data/lib/sus/output/bar.rb +17 -0
- data/lib/sus/output/buffered.rb +32 -1
- data/lib/sus/output/lines.rb +10 -0
- data/lib/sus/output/messages.rb +26 -3
- data/lib/sus/output/null.rb +16 -2
- data/lib/sus/output/progress.rb +29 -1
- data/lib/sus/output/status.rb +13 -0
- data/lib/sus/output/structured.rb +14 -1
- data/lib/sus/output/text.rb +33 -1
- data/lib/sus/output/xterm.rb +11 -1
- data/lib/sus/output.rb +9 -0
- data/lib/sus/raise_exception.rb +16 -0
- data/lib/sus/receive.rb +82 -0
- data/lib/sus/registry.rb +20 -1
- data/lib/sus/respond_to.rb +29 -2
- data/lib/sus/shared.rb +16 -0
- data/lib/sus/tree.rb +10 -0
- data/lib/sus/version.rb +2 -1
- data/lib/sus/with.rb +18 -0
- data/readme.md +8 -0
- data/releases.md +4 -0
- data.tar.gz.sig +0 -0
- metadata +3 -3
- metadata.gz.sig +0 -0
- data/context/usage.md +0 -380
data/lib/sus/respond_to.rb
CHANGED
|
@@ -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
|
-
#
|
|
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
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
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.
|
|
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.
|