Fresh start

This commit is contained in:
Michael Miller 2024-06-06 16:25:39 -06:00
parent 287758e6af
commit 10a0ccc8bd
No known key found for this signature in database
GPG key ID: 32B47AE8F388A1FF
292 changed files with 43 additions and 26536 deletions

9
.gitignore vendored
View file

@ -4,11 +4,12 @@
/.shards/
*.dwarf
# Libraries don't need dependency lock
# Dependencies will be locked in application that uses them
# Libraries don't need dependency lock.
# Dependencies will be locked in applications that use them.
/shard.lock
# Ignore JUnit output
# Ignore JUnit output.
output.xml
/test.cr
# Ignore ad-hoc test files.
/*test*.cr

View file

@ -1,89 +0,0 @@
# Official language image. Look for the different tagged releases at:
# https://hub.docker.com/r/crystallang/crystal/
image: "crystallang/crystal:latest"
# Cache shards in between builds
cache:
paths:
- lib
- bin
before_script:
- crystal -v # Print out Crystal version for debugging
spec:
script:
- crystal spec --error-on-warnings --junit_output=. spec/matchers/ spec/spectator/*.cr
artifacts:
when: always
paths:
- output.xml
reports:
junit: output.xml
spec docs:
extends: spec
script:
- crystal spec --error-on-warnings --junit_output=. spec/docs/
spec features:
extends: spec
script:
- crystal spec --error-on-warnings --junit_output=. spec/features/
spec regression:
extends: spec
script:
- crystal spec --error-on-warnings --junit_output=. spec/issues/
spec rspec:
extends: spec
script:
- crystal spec --error-on-warnings --junit_output=. spec/rspec/
spec dsl:
extends: spec
script:
- crystal spec --error-on-warnings --junit_output=. spec/spectator/dsl/
spec mocks:
extends: spec
script:
- crystal spec --error-on-warnings --junit_output=. spec/spectator/mocks/
format:
script:
- shards
- crystal tool format --check
style:
script:
- shards
- bin/ameba
nightly:
image: "crystallang/crystal:nightly"
allow_failure: true
script:
- shards --ignore-crystal-version
- crystal spec --error-on-warnings --junit_output=. --tag smoke spec/spectator/dsl/
- crystal tool format --check
artifacts:
when: always
paths:
- output.xml
reports:
junit: output.xml
pages:
stage: deploy
dependencies:
- spec
script:
- crystal docs
- mv docs/ public/
artifacts:
paths:
- public
only:
- master

View file

@ -1,11 +0,0 @@
files: ./src/**/*.cr
run: time crystal spec --error-trace
---
files: ./src/**/*.cr
run: bin/ameba %file%
---
files: ./spec/**/*.cr
run: time crystal spec --error-trace %file%
---
files: ./shard.yml
run: shards

View file

@ -1,273 +0,0 @@
# Architecture and Design of Spectator
This document explains the structure and design decisions behind Spectator.
It is broken up into the logical components of Spectator:
- [Terms](#terms)
- [DSL](#dsl) - Domain Specific Language. Macros and methods that build a spec.
- [Matchers](#matchers)
- [Examples and groups](#examples-and-groups)
- [Runner and harness](#runner-and-harness)
- [Hooks](#hooks)
- [Mocks and doubles](#mocks-and-doubles)
- [Stubs](#stubs)
- [Doubles](#doubles)
- [Formatting](#formatting)
## Terms
The following are terms and concepts frequently used in the project.
They are listed in alphabetical order,
but you may find it useful to jump around when learning what they mean.
**Assertion**
An *assertion* is a fundamental piece of a test.
It checks that a condition is satisfied.
If that condition isn't met, then an exception is raised.
**Builder**
A *builder* is a type that incrementally constructs a complex object.
Builders are primarily used to create *specs* and *example groups*.
See: https://sourcemaking.com/design_patterns/builder
**Config**
Short for *configuration*, a *config* stores information about how to run the *spec*.
The configuration includes parsed command-line options, settings from `.spectator` and `Spectator.configure`.
**Context**
A *context* is the scope or "environment" a test runs in.
It is a part of an *example group* that provides methods, memoized values, and more to an example block.
From a technical standpoint, it is typically an instance of the class defined by an `example_group` block.
It can thought of as a closure.
**Double**
Stand-in for another type.
*Doubles* can be passed to methods under test instead of a real object.
They can be configured to respond to methods with *stubs*.
*Doubles* also track calls made to them.
An important note: a *double* is _not_ the same type (nor does it inherit) the replaced type.
**DSL**
*DSL* stands for **D**omain **S**pecific **L**anguage.
It is the human-like language that comprises a *spec*.
Keywords in the *DSL*, such as `describe`, `it`, and `expect`, are macros or methods.
Those macros and methods make calls to Spectator to describe the structure of a *spec*.
They are combined in such a way that makes it easy to read.
**Example**
An *example* is essentially a *test* and metadata.
Spectator makes a distinction between *test* and *example*.
An *example* can have a description, *group*, *context*, *result*, and *tags*.
That is to say: an *example* is the *test* and information for execution.
An *example* is a type of *node*.
In the *DSL*, an *example* is created with `example` and `it` blocks and their variants.
**Example Group**
An *example group* (or *group* for short), is a collection of *examples*.
*Groups* can be nested in other *groups*, but can only have one parent.
*Groups* can have *hooks*.
*Groups* have extra properties like a name and metadata.
A *group* is a type of *node*.
In the *DSL*, an *example group* is created with `example_group`, `describe`, and `context` blocks and their variants.
**Expectation**
An *expectation* captures a value or behavior and whether it satisfies a condition.
*Expectations* contain *match data*.
They are bubbled up from the *harness* to the runner.
*Expectations* can be thought of as wrappers for *assertions*.
In the *DSL*, an *expectation* is the code: `expect(THIS).to eq(THAT)`
An *expectation target* is just the `expect(THIS)` portion.
**Formatter**
A *formatter* takes *results* and reports them to the user in a specific format.
Examples of *formatters* are XML, HTML, JSON, dots, and documentation.
The runner will call methods on the *formatter*.
The methods called depend on the type of *result* and the state of the runner.
For instance, `#example_started` is called before a an example runs,
and `#dump_summary` is called at the end when all *results* are available.
**Harness**
A *harness* is used to safely wrap *test* code.
It captures *expectations* and creates a *result* based on the outcome.
**Filter**
A *filter* selects *nodes* to be included in a running *spec*.
There are multiple types of *filters*.
**Hook**
A *hook* is a piece of code to execute at a key time.
For instance, before a *test* starts, or after everything in a *group* completes.
*Hooks* can be run in the same *context* as a *test*.
These are known as "example hooks."
*Hooks* that don't run in a *context*, and instead run independent of *examples*, are called "example group hooks."
*Hooks* are attached to *groups*.
**Label**
A *label* is a string from the *spec* that identifies a expression.
*Labels* are captured to improve the readability of *results* and *match data*.
In the following code, the labels are: `does something useful`, `the_answer`, and `42`.
```crystal
it "does something useful" do
expect(the_answer).to eq(42)
end
```
**Matcher**
A *matcher* defines an expected value or behavior.
*Matchers* are given an "actual" value from a test and produce *match data*.
The *match data* contains information regarding whether the value or behavior was expected (satisfies a condition).
They behave similarly to an instance of a `Regex`.
In the following code, the `eq(42)` portion returns an instance of a *matcher* expecting the value 42.
```crystal
expect(the_answer).to eq(42)
```
**Match Data**
*Match data* is produced by *matchers*.
It contains information regarding whether an *expectation* is satisfied and values from the match.
The values are key-value pairs identifying things such as "expected value" and "actual value."
*Match data* is similar in concept to `Regex::MatchData`.
**Mock**
A *mock* is a type that can have its original functionality "swapped out" for a *stub*.
This allows complex types to be "mocked" so that other types can be unit tested.
*Mocks* can have any number of *stubs* defined.
They are similar to *doubles*, but use a real type.
**Node**
A *node* refers to any component in a *spec*.
*Nodes* are typically *examples* and *example groups*.
A *node* can have metadata associated with it, such as a *label*, location, and *tags*.
**Procsy**
A *procsy* is a glorified `Proc`.
It is used to wrap an underlying proc in some way.
Typically used to wrap an *example* when passed to a *hook*.
**Profile**
A *profile* includes timing information for *examples*.
It tracks how long each *example* took and sorts them.
**Report**
A *report* is a collection of *results* generated by running *examples*.
It provides easy access to various metrics and types of *results*.
**Result**
A *result* summarizes the outcome of running an *example*.
A *result* can be passing, failing, or pending.
*Results* contain timing information, *expectations* processed in the *example*, and an error for failing *results*.
**Spec**
A *spec* is a collection of *examples*.
Conceptually, a *spec* defines the behavior of a system.
A *spec* consists of a single, root *example group* that provides a tree structure of *examples*.
A *spec* also contains some *config* describing how to run it.
**Stub**
A *stub* is a method in a *double* or *mock* that replaces the original functionality.
*Stubs* can be attached to a single instance or all instances of a type.
**Tag**
A *tag* is an identifier with optional value.
*Tags* can be used to group and filter *examples* and *example groups*.
Some *tags* have special meaning, like `skip` indicating an *example* or *group* should be skipped.
**Test**
The word "test" is overused, especially when using a testing framework.
We make an effort to avoid using the word "test" for everything.
However, *test* has a technical meaning in Spectator.
It refers to the code (block) executed in an *example*.
```crystal
it "does a thing" do
# Test code starts here. Everything inside this `it` block is considered a test.
expect(the_answer).to eq(42)
# Test code ends here.
end
```
## DSL
The DSL is made up of methods and macros.
What look like keywords (`describe`, `it`, `expect`, `eq`, etc.) are just macros and methods provided by the DSL.
Those macros and methods are defined in multiple modules in the `Spectator::DSL` namespace.
They are logically grouped by their functionality.
Each module is included (as a mix-in) to the base Spectator context that all tests use.
The `SpectatorTestContext` class includes all of the DSL modules.
The DSL methods and macros should be kept simple.
Their functionality should be off-loaded to internal Spectator "APIs."
For instance, when the DSL creates an example,
it defines a method for the test code and calls another method to register it.
While Crystal macros are powerful, excessive use of them makes maintenance harder and compilation slower.
Additionally, by keeping logic out of the DSL, testing of internals becomes less dependent on the DSL.
*TODO:* Builders...
*TODO:* Tricks...
## Matchers
*TODO:* Base types
## Examples and groups
*TODO*
## Runner and harness
*TODO*
### Hooks
*TODO*
## Mocks and doubles
*TODO*
### Stubs
*TODO*
### Doubles
*TODO*
## Formatting
*TODO*

View file

@ -1,530 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.12.0] - 2024-02-03
### Added
- Added ability to use matchers for case equality. [#55](https://github.com/icy-arctic-fox/spectator/issues/55)
- Added support for nested case equality when checking arguments with Array, Tuple, Hash, and NamedTuple.
### Fixed
- Fixed some issues with the `be_within` matcher when used with expected and union types.
## [0.11.7] - 2023-10-16
### Fixed
- Fix memoized value (`let`) with a union type causing segfault. [#81](https://gitlab.com/arctic-fox/spectator/-/issues/81)
## [0.11.6] - 2023-01-26
### Added
- Added ability to cast types using the return value from expect/should statements with a type matcher.
- Added support for string interpolation in context names/labels.
### Fixed
- Fix invalid syntax (unterminated call) when recording calls to stubs with an un-named splat. [#51](https://github.com/icy-arctic-fox/spectator/issues/51)
- Fix malformed method signature when using named splat with keyword arguments in mocked type. [#49](https://github.com/icy-arctic-fox/spectator/issues/49)
### Changed
- Expectations using 'should' syntax report file and line where the 'should' keyword is instead of the test start.
- Add non-captured block argument in preparation for Crystal 1.8.0.
## [0.11.5] - 2022-12-18
### Added
- Added support for mock modules and types that include mocked modules.
### Fixed
- Fix macro logic to support free variables, 'self', and variants on stubbed methods. [#48](https://github.com/icy-arctic-fox/spectator/issues/48)
- Fix method stubs used on methods that capture blocks.
- Fix type name resolution for when using custom types in a mocked typed.
- Prevent comparing range arguments with non-compatible types in stubs. [#48](https://github.com/icy-arctic-fox/spectator/issues/48)
### Changed
- Simplify string representation of mock-related types.
- Remove unnecessary redefinitions of methods when adding stub functionality to a type.
- Allow metadata to be stored as nil to reduce overhead when tracking nodes without tags.
- Use normal equality (==) instead of case-equality (===) with proc arguments in stubs.
- Change stub value cast logic to avoid compiler bug. [#80](https://gitlab.com/arctic-fox/spectator/-/issues/80)
## [0.11.4] - 2022-11-27
### Added
- Add support for using named (keyword) arguments in place of positional arguments in stubs. [#47](https://github.com/icy-arctic-fox/spectator/issues/47)
- Add `before`, `after`, and `around` as aliases for `before_each`, `after_each`, and `around_each` respectively.
### Fixed
- Clear stubs defined with `expect().to receive()` syntax after test finishes to prevent leakage between tests.
- Ensure stubs defined with `allow().to receive()` syntax are cleared after test finishes when used inside a test (another leakage).
- Fix crash caused when logging is enabled after running an example that attempts to exit.
### Removed
- Removed support for stubbing undefined (untyped) methods in lazy doubles. Avoids possible segfault.
## [0.11.3] - 2022-09-03
### Fixed
- Display error block (failure message and stack trace) when using `fail`. [#78](https://gitlab.com/arctic-fox/spectator/-/issues/78)
- Defining a custom matcher outside of the `Spectator` namespace no longer produces a compilation error. [#46](https://github.com/icy-arctic-fox/spectator/issues/46)
## [0.11.2] - 2022-08-07
### Fixed
- `expect_raises` with block and no arguments produces compilation error. [#77](https://gitlab.com/arctic-fox/spectator/-/issues/77)
### Changed
- `-e` (`--example`) CLI option performs a partial match instead of exact match. [#71](https://gitlab.com/arctic-fox/spectator/-/issues/71) [#45](https://github.com/icy-arctic-fox/spectator/issues/45)
## [0.11.1] - 2022-07-18
### Fixed
- Workaround nilable type issue with memoized value. [#76](https://gitlab.com/arctic-fox/spectator/-/issues/76)
## [0.11.0] - 2022-07-14
### Changed
- Overhauled mock and double system. [#63](https://gitlab.com/arctic-fox/spectator/-/issues/63)
- Testing if `exit` is called no longer is done with stubs and catching the `Spectator::SystemExit` exception should be caught. [#29](https://github.com/icy-arctic-fox/spectator/issues/29)
- Adjust evaluation order of `change` matcher expressions.
### Removed
- Removed support for stubbing top-level methods (such as `exit`).
## [0.10.6] - 2022-07-07
### Fixed
- Fixed compiler warnings generated by positional arguments with different names.
### Changed
- Forward example procsy `to_s` to underlying example. [#70](https://gitlab.com/arctic-fox/spectator/-/issues/70)
## [0.10.5] - 2022-01-27
### Fixed
- Fixed usage of `sample` with single block argument. [#41](https://github.com/icy-arctic-fox/spectator/issues/41#issuecomment-1022525702)
## [0.10.4] - 2022-01-11
### Added
- Support string interpolation for example name/description. [#41](https://github.com/icy-arctic-fox/spectator/issues/41)
- Support multiple block arguments in `sample` block (`Hash#each`). [#41](https://github.com/icy-arctic-fox/spectator/issues/41#issuecomment-1010192486)
### Changed
- Source line reported by failure list changed to line containing `expect` instead of example start line.
- Better compiler error when using string interpolation in group name/description. [#41](https://github.com/icy-arctic-fox/spectator/issues/41)
## [0.10.3] - 2021-10-13
### Fixed
- Fixed runtime error with `expect` outside of test block - now gives compilation error.
### Added
- Description of a `provided` example can be set by using `it` as an argument. [#69](https://gitlab.com/arctic-fox/spectator/-/issues/69)
## [0.10.2] - 2021-10-22
### Fixed
- Fix usage of `be ===` and `be =~` [#34](https://github.com/icy-arctic-fox/spectator/issues/34)
- Better handling of the `be(nil)` when used with value types. [#37](https://github.com/icy-arctic-fox/spectator/issues/37)
- Fix missing arguments for stubbed top-level methods (`system`, `exit`, etc.). [#36](https://github.com/icy-arctic-fox/spectator/issues/36)
- Fix outdated naming when using `expect_any_instance_of`.
- Fix adding stubs to class methods on mocked types.
### Changed
- Elegantly handle missing/undefined methods with `have_attributes` matcher.
## [0.10.1] - 2021-09-16
### Fixed
- Fix `Spectator.configure` block calls to `filter_run_excluding` and `filter_run_including`. [#61](https://gitlab.com/arctic-fox/spectator/-/issues/61)
- Fix shard version constant creation when lib is in a directory with spaces in the path. [#33](https://gitlab.com/arctic-fox/spectator/-/merge_requests/33) Thanks @toddsundsted !
- Re-add pre- and post-condition hooks. [#62](https://gitlab.com/arctic-fox/spectator/-/issues/62)
## [0.10.0] - 2021-08-19
### Fixed
- Fix resolution of types with the same name in nested scopes. [#31](https://github.com/icy-arctic-fox/spectator/issues/31)
- `around_each` hooks wrap `before_all` and `after_all` hooks. [#12](https://github.com/icy-arctic-fox/spectator/issues/12)
- Hook execution order has been tweaked to match RSpec.
### Added
- `before_each`, `after_each`, and `around_each` hooks are yielded the current example as a block argument.
- The `let` and `subject` blocks are yielded the current example as a block argument.
- Add internal logging that uses Crystal's `Log` utility. Provide the `LOG_LEVEL` environment variable to enable.
- Support dynamic creation of examples.
- Capture and log information for hooks.
- Tags can be added to examples and example groups.
- Add matcher to check compiled type of values.
- Examples can be skipped by using a `:pending` tag. A reason method can be specified: `pending: "Some excuse"`
- Examples without a test block are marked as pending. [#37](https://gitlab.com/arctic-fox/spectator/-/issues/37)
- Examples can be skipped during execution by using `skip` or `pending` in the example block. [#17](https://gitlab.com/arctic-fox/spectator/-/issues/17)
- Sample blocks can be temporarily skipped by using `xsample` or `xrandom_sample`.
- Add `before_suite` and `after_suite` hooks. [#21](https://gitlab.com/arctic-fox/spectator/-/issues/21)
- Support defining hooks in `Spectator.configure` block. [#21](https://gitlab.com/arctic-fox/spectator/-/issues/21)
- Examples with failures or skipped during execution will report the location of that result. [#57](https://gitlab.com/arctic-fox/spectator/-/issues/57)
- Support custom messages for failed expectations. [#28](https://gitlab.com/arctic-fox/spectator/-/issues/28)
- Allow named arguments and assignments for `provided` (`given`) block.
- Add `aggregate_failures` to capture and report multiple failed expectations. [#24](https://gitlab.com/arctic-fox/spectator/-/issues/24)
- Supports matching groups. [#25](https://gitlab.com/arctic-fox/spectator/-/issues/25) [#24](https://github.com/icy-arctic-fox/spectator/issues/24)
- Add `filter_run_including`, `filter_run_excluding`, and `filter_run_when_matching` to config block.
- By default, only run tests when any are marked with `focus: true`.
- Add "f-prefix" blocks for examples and groups (`fit`, `fdescribe`, etc.) as a short-hand for specifying `focus: true`.
- Add HTML formatter. Operates the same as the JUnit formatter. Specify `--html_output=DIR` to use. [#22](https://gitlab.com/arctic-fox/spectator/-/issues/22) [#3](https://github.com/icy-arctic-fox/spectator/issues/3)
### Changed
- `given` (now `provided`) blocks changed to produce a single example. `it` can no longer be nested in a `provided` block.
- The "should" syntax no longer reports the source as inside Spectator.
- Short-hand "should" syntax must be included by using `require "spectator/should"` - `it { should eq("foo") }`
- Better error messages and detection when DSL methods are used when they shouldn't (i.e. `describe` inside `it`).
- Prevent usage of reserved keywords in DSL (such as `initialize`).
- The count argument for `sample` and `random_sample` groups must be named (use `count: 5` instead of just `5`).
- Helper methods used as arguments for `sample` and `random_sample` must be class methods.
- Simplify and reduce instanced types and generics. Should speed up compilation times.
- Overhaul example creation and handling.
- Overhaul storage of test values.
- Overhaul reporting and formatting. Cleaner output for failures and pending tests.
- Cleanup and simplify DSL implementation.
- Other minor internal improvements and cleanup.
### Deprecated
- `pending` blocks will behave differently in v0.11.0. They will mimic RSpec in that they _compile and run_ the block expecting it to fail. Use a `skip` (or `xit`) block instead to prevent compiling the example.
- `given` has been renamed to `provided`. The `given` keyword may be reused later for memoization.
### Removed
- Removed one-liner `it`-syntax without braces (block).
## [0.9.40] - 2021-07-10
### Fixed
- Fix stubbing of class methods.
- Fix handling of `no_args` in some cases.
### Changed
- Better handling and stubbing of `Process.exit`.
## [0.9.39] - 2021-07-02
### Fixed
- Fix `expect().to receive()` syntax not implicitly stubbing the method.
- Avoid calling `NoReturn` methods from stubs. [#29](https://github.com/icy-arctic-fox/spectator/issues/29)
### Added
- Added support for `with(no_args)` for method stubs. [#28](https://github.com/icy-arctic-fox/spectator/issues/28)
- Allow creation of doubles without definition block. [#30](https://github.com/icy-arctic-fox/spectator/issues/30)
## [0.9.38] - 2021-05-27
### Fixed
- Fix `Channel::ClosedError` when using default Crystal Logger. [#27](https://github.com/icy-arctic-fox/spectator/issues/27)
## [0.9.37] - 2021-05-19
### Added
- Added support for `be ===` and `be =~`. [#26](https://github.com/icy-arctic-fox/spectator/issues/26)
## [0.9.36] - 2021-04-22
### Fixed
- Remove old workaround that prevented compilation on Windows. [#58](https://gitlab.com/arctic-fox/spectator/-/issues/58)
## [0.9.35] - 2021-04-18
### Fixed
- Allow types stored in variables or returned by methods in `be_a` (and variants), not just type literals. [#25](https://github.com/icy-arctic-fox/spectator/issues/25)
## [0.9.34] - 2021-03-31
### Changed
- Allow filtering examples by using any line in the example block. [#19](https://github.com/icy-arctic-fox/spectator/issues/19) Thanks @matthewmcgarvey !
## [0.9.33] - 2021-03-22
### Changed
- Target Crystal 1.0
## [0.9.32] - 2021-02-03
### Fixed
- Fix source reference with brace-less example syntax. [#20](https://github.com/icy-arctic-fox/spectator/issues/20)
## [0.9.31] - 2021-01-08
### Fixed
- Fix misaligned line numbers when referencing examples and groups.
## [0.9.30] - 2020-12-23
### Fixed
- Fix issue caused by additions from 0.9.29.
### Changed
- Improve the `contain`, `contain_elements`, `have`, and `have_elements` to show missing items in output.
## [0.9.29] - 2020-12-23
### Added
- Add variants `contain_elements` and `have_elements`, which behave like `contain` and `have` matchers except that they take an array (or any enumerable type) instead of a parameter list or splat.
## [0.9.28] - 2020-11-07
### Added
- Add `return_type` option to method stubs.
## [0.9.27] - 2020-10-01
### Added
- Add syntax for stubbing operator-style methods, such as `[]`.
## [0.9.26] - 2020-09-27
### Fixed
- Fix issue with yielding in stubbed mocks.
## [0.9.25] - 2020-09-26
### Fixed
- Fix issue with splatting values for failed match data. This prevented the use of "description" and "failure_message" in some matches like `respond_to`.
## [0.9.24] - 2020-09-17
### Changed
- Allow some forms of string interpolation in group and example descriptions.
## [0.9.23] - 2020-08-30
### Fixed
- Allow the use of `object_id` and other possibly conflicting method names via `let`. [#53](https://gitlab.com/arctic-fox/spectator/-/issues/53)
## [0.9.22] - 2020-08-11
### Changed
- Handle splat in macro for matcher DSL. [#8](https://github.com/icy-arctic-fox/spectator/issues/8)
## [0.9.21] - 2020-07-27
### Added
- Display random seed when using `-r` or `--seed` options. [#7](https://github.com/icy-arctic-fox/spectator/issues/7)
## [0.9.20] - 2020-05-29
### Fixed
- Fix bug when using multiple short-hand block expects in one test.
## [0.9.19] - 2020-05-28
### Fixed
- Fix issue with `match_array` and `contain_exactly` matchers not working with immutable collections.
## [0.9.18] - 2020-04-26
### Fixed
- Fix `describe_class.new` when using a generic type.
## [0.9.17] - 2020-04-23
### Fixed
- Fix issue when using deferred syntax with `receive` matcher. [#48](https://gitlab.com/arctic-fox/spectator/-/issues/48)
## [0.9.16] - 2020-04-06
### Fixed
- Silence warnings from Crystal 0.34
## [0.9.15] - 2020-04-03
### Fixed
- Fix issues with `match_array().in_any_order` and `contain_exactly().in_any_order`. [#47](https://gitlab.com/arctic-fox/spectator/-/issues/47)
### Changed
- Improve usability when actual value does not respond to methods needed to verify it.
For instance, `expect(nil).to contain_exactly(:foo)` would not compile.
This has been changed so that it compiles and raises an error at runtime with a useful message.
## [0.9.14] - 2020-04-01
### Fixed
- Fix using nil with `be` matcher. [#45](https://gitlab.com/arctic-fox/spectator/-/issues/45)
## [0.9.13] - 2020-03-28
### Fixed
- Fix arguments not found in default stubs for mocks. [#44](https://gitlab.com/arctic-fox/spectator/-/issues/44)
## [0.9.12] - 2020-03-20
### Fixed
- Fix issue when mocking modules. Thanks @watzon !
## [0.9.11] - 2020-03-04
### Fixed
- Fix issue when describing constants. [#40](https://gitlab.com/arctic-fox/spectator/-/issues/40) [#41](https://gitlab.com/arctic-fox/spectator/-/issues/41)
## [0.9.10] - 2020-03-03
### Changed
- Smarter behavior when omitting the block argument to the `around_each` hook.
## [0.9.9] - 2020-02-22
### Fixed
- Fix implicit subject when used with a module. [#6](https://github.com/icy-arctic-fox/spectator/issues/6)
## [0.9.8] - 2020-02-21
### Fixed
- Fix `be_between` matcher. Thanks @davidepaolotua / @jinn999 !
## [0.9.7] - 2020-02-16
### Fixed
- Fix memoization of subject when using a type name for the context.
- Fix some cases when mocking a class method.
## [0.9.6] - 2020-02-10
### Added
- Add short-hand "should" syntax - `it { should eq("foo") }`
- The `be` matcher can be used on value types.
- Add more tests cases from RSpec docs.
### Fixed
- Fix an issue with stubbed class methods on mocked types. Sometimes `previous_def` was used when `super` should have been used instead.
- Fix deferred expectations not running after all hooks.
## [0.9.5] - 2020-01-19
### Changed
- Described type is now considered an explicit subject.
## [0.9.4] - 2020-01-19
### Added
- Add more test cases from RSpec docs.
- Add `it_fails` utility to test expected failures.
### Fixed
- Fix negated case for `respond_to` matcher.
## [0.9.3] - 2020-01-17
### Fixed
- Fix implicit subject overwriting explicit subject. [#25](https://gitlab.com/arctic-fox/spectator/-/merge_requests/25)
## [0.9.2] - 2020-01-14
### Added
- Add tests from RSpec docs.
- Add `with_message` modifier for `raise_error` matcher.
- Support omitted description on `it` and `specify` blocks. Use matcher description by default.
### Fixed
- Fix `let!` not inferring return type. [#4](https://github.com/icy-arctic-fox/spectator/issues/4)
### Changed
- Modified some matchers to behave more closely to their RSpec counterparts.
## [0.9.1] - 2019-12-13
### Fixed
- Fix default stub with type.
- Fix verifying double on self argument type.
- Pass stub instead of stub name to mock registry.
### Removed
- Remove unnecessary type from stub class hierarchy.
## [0.9.0] - 2019-12-08
### Added
- Implement initial mocks and doubles (stubbing) support. [#16](https://gitlab.com/arctic-fox/spectator/-/merge_requests/16) [#6](https://gitlab.com/arctic-fox/spectator/-/issues/6)
- Deferred expectations (`to_eventually` and `to_never`).
### Changed
- Test cases no longer define an entire class, but rather a method in a class belonging to the group.
## [0.8.3] - 2019-09-23
### Fixed
- Fix and address warnings with Crystal 0.31.0.
## [0.8.2] - 2019-08-21
### Fixed
- Workaround for Crystal compiler bug [#7060](https://github.com/crystal-lang/crystal/issues/7060). [#1](https://github.com/icy-arctic-fox/spectator/issues/1)
## [0.8.1] - 2019-08-17
### Fixed
- Fix nested `sample_value` blocks giving cryptic error. [#20](https://gitlab.com/arctic-fox/spectator/-/issues/20)
## [0.8.0] - 2019-08-12
### Added
- Add "any order" modifier for `contains_exactly` and `match_array`.
- Add `change` matcher and its variations.
- Add `all` matcher.
- Variation of `let` syntax that takes an assignment.
### Changed
- Rewrote matcher class structure.
- Improved tracking of actual and expected values and their labels.
- Matcher values are only produced when the match fails, instead of always.
### Fixed
- Fix malformed code generated by macros not working in latest Crystal version.
## [0.7.2] - 2019-06-01
### Fixed
- Reference types used in `subject` and `let` were recreated between hooks and the test block. [#11](https://gitlab.com/arctic-fox/spectator/-/issues/11)
## [0.7.1] - 2019-05-21
### Fixed
- Fixed an issue where named subjects could crash the compiler.
## [0.7.0] - 2019-05-16
### Added
- Added `be_between` matcher.
### Changed
- The `be_within` matcher behaves like RSpec's.
## [0.6.0] - 2019-05-08
### Changed
- Introduced reference matcher and changed `be` matcher to use it instead of the case matcher.
### Removed
- Removed regex matcher, the case matcher is used instead.
## [0.5.3] - 2019-05-08
### Fixed
- Updated the `expect_raises` matcher to accept an optional second argument to mimic `raise_error`. [#4](https://gitlab.com/arctic-fox/spectator/-/issues/4)
## [0.5.2] - 2019-04-22
### Fixed
- Fix `after_all` hooks not running with fail-fast enabled. [#2](https://gitlab.com/arctic-fox/spectator/-/issues/2)
## [0.5.1] - 2019-04-18
### Added
- Note in README regarding repository mirror.
### Fixed
- Change protection on expectation partial to work with Crystal 0.28 and "should" syntax.
- Change references to `Time.now` to `Time.utc` in docs.
## [0.5.0] - 2019-04-07
First version ready for public use.
[Unreleased]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.12.0...master
[0.12.0]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.7...v0.12.0
[0.11.7]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.6...v0.11.7
[0.11.6]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.5...v0.11.6
[0.11.5]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.4...v0.11.5
[0.11.4]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.3...v0.11.4
[0.11.3]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.2...v0.11.3
[0.11.2]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.1...v0.11.2
[0.11.1]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.0...v0.11.1
[0.11.0]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.10.6...v0.11.0
[0.10.6]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.10.5...v0.10.6
[0.10.5]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.10.4...v0.10.5
[0.10.4]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.10.3...v0.10.4
[0.10.3]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.10.2...v0.10.3
[0.10.2]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.10.1...v0.10.2
[0.10.1]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.10.0...v0.10.1
[0.10.0]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.40...v0.10.0
[0.9.40]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.39...v0.9.40
[0.9.39]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.38...v0.9.39
[0.9.38]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.37...v0.9.38
[0.9.37]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.36...v0.9.37
[0.9.36]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.35...v0.9.36
[0.9.35]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.34...v0.9.35
[0.9.34]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.33...v0.9.34
[0.9.33]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.32...v0.9.33
[0.9.32]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.31...v0.9.32
[0.9.31]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.30...v0.9.31
[0.9.30]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.29...v0.9.30
[0.9.29]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.28...v0.9.29
[0.9.28]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.27...v0.9.28
[0.9.27]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.26...v0.9.27
[0.9.26]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.25...v0.9.26
[0.9.25]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.24...v0.9.25
[0.9.24]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.23...v0.9.24
[0.9.23]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.22...v0.9.23
[0.9.22]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.21...v0.9.22
[0.9.21]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.20...v0.9.21
[0.9.20]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.19...v0.9.20
[0.9.19]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.18...v0.9.19
[0.9.18]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.17...v0.9.18
[0.9.17]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.16...v0.9.17
[0.9.16]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.15...v0.9.16
[0.9.15]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.14...v0.9.15
[0.9.14]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.13...v0.9.14
[0.9.13]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.12...v0.9.13
[0.9.12]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.11...v0.9.12
[0.9.11]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.10...v0.9.11
[0.9.10]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.9...v0.9.10
[0.9.9]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.8...v0.9.9
[0.9.8]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.7...v0.9.8
[0.9.7]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.6...v0.9.7
[0.9.6]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.5...v0.9.6
[0.9.5]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.4...v0.9.5
[0.9.4]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.3...v0.9.4
[0.9.3]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.2...v0.9.3
[0.9.2]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.1...v0.9.2
[0.9.1]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.9.0...v0.9.1
[0.9.0]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.8.3...v0.9.0
[0.8.3]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.8.2...v0.8.3
[0.8.2]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.8.1...v0.8.2
[0.8.1]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.8.0...v0.8.1
[0.8.0]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.7.2...v0.8.0
[0.7.2]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.7.1...v0.7.2
[0.7.1]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.7.0...v0.7.1
[0.7.0]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.6.0...v0.7.0
[0.6.0]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.5.2...v0.6.0
[0.5.3]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.5.2...v0.5.3
[0.5.2]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.5.1...v0.5.2
[0.5.1]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.5.0...v0.5.1
[0.5.0]: https://gitlab.com/arctic-fox/spectator/-/releases/v0.5.0

View file

@ -1,204 +0,0 @@
# Contributing to Spectator
Welcome to Spectator! We're glad you're here!
Spectator strives to be an easy-to-use, batteries included testing framework for Crystal shards and applications.
The goal of Spectator is to:
- Provide an easy-to-understand syntax for tests. Reading and writing tests should feel natural.
- Lower the bar to entry. Simplify non-trivial use cases make testing easier.
- Remove boilerplate. Reduce the amount of code necessary to get the job done. Provide common utilities.
Spectator is heavily inspired by [RSpec](https://rspec.info/).
It tries to maintain compatibility with RSpec, but this isn't always possible.
Some language differences between [Ruby and Crystal](https://www.crystalforrubyists.com/) prohibit this.
Spectator also maintains feature parity with Crystal's [Spec](https://crystal-lang.org/reference/guides/testing.html).
**Table of Contents**
- [Useful links](#useful-links)
- [Repository hosting](#repository-hosting)
- [How can I contribute?](#how-can-i-contribute)
- [Upcoming](#upcoming)
- [How to submit changes](#how-to-submit-changes)
- [How to report a bug](#how-to-report-a-bug)
- [How to request an enhancement](#how-to-request-an-enhancement)
- [Where can I get help?](#where-can-i-get-help)
- [Development](#development)
- [Testing](#testing)
- [Style guide and conventions](#style-guide-and-conventions)
- [Branching](#branching)
## Useful links
Here are some useful links for Spectator:
- [README](README.md)
- [Change log](CHANGELOG.md)
- [Architecture](ARCHITECTURE.md)
- Wiki [GitLab](https://gitlab.com/arctic-fox/spectator/-/wikis/home) | [GitHub](https://github.com/icy-arctic-fox/spectator/wiki)
- Big List of Matchers [GitLab](https://gitlab.com/arctic-fox/spectator/-/wikis/Big-List-of-Matchers) | [GitHub](https://github.com/icy-arctic-fox/spectator/wiki/Big-List-of-Matchers)
- [Documentation (Crystal docs)](https://arctic-fox.gitlab.io/spectator)
- Issue tracker [GitLab](https://gitlab.com/arctic-fox/spectator/-/issues) | [GitHub](https://github.com/icy-arctic-fox/spectator/issues)
- [Builds](https://gitlab.com/arctic-fox/spectator/-/pipelines)
- [Crystal Macros](https://crystal-lang.org/reference/syntax_and_semantics/macros/index.html)
- [API](https://crystal-lang.org/api/latest/Crystal/Macros.html)
## Repository hosting
Spectator is available on [GitHub](https://github.com/icy-arctic-fox/spectator) and [GitLab](https://gitlab.com/arctic-fox/spectator).
The primary housing for Spectator is [GitLab](https://gitlab.com/arctic-fox/spectator).
This is a preference of the primary developer ([arctic-fox](https://gitlab.com/arctic-fox)).
That doesn't mean GitLab is the only place to contribute!
The repository and wiki are mirrored and maintainers will ensure contributions get into the project.
Issues, pull requests, and other contributions are accepted on both.
Use whichever you prefer!
## How can I contribute?
You can contribute in a variety of ways.
One of the easiest ways to contribute is to use Spectator!
Provide us feedback and report and bugs or issues you find.
Being a testing framework, Spectator should be rock solid.
If you want to contribute to the codebase, take a look at the open issues.
[GitLab](https://gitlab.com/arctic-fox/spectator/-/issues) | [GitHub](https://github.com/icy-arctic-fox/spectator/issues)
Any issue not assigned can be picked up.
Even if one is already assigned, you may still be able to help out, just ask!
If there isn't an issue for something you want to add or change, please create one first.
That way, we can discuss it before possibly wasting time on a misunderstanding.
Check out the [upcoming](#upcoming) section for a list of upcoming changes.
Anyone submitting code to Spectator will get a call-out in the [change log](CHANGELOG.md).
You can also help by writing tests or documentation for the wiki.
### Upcoming
These are the projects that Spectator maintainers are looking to tackle next.
These issues are quite substantial, but assistance would be greatly appreciated!
- [Overhaul of the mock and stubbing system](https://gitlab.com/arctic-fox/spectator/-/issues/63)
- [Errors masked in DSL by `method_missing` (predicate matcher)](https://gitlab.com/arctic-fox/spectator/-/issues/64)
- [Custom matcher DSL](https://gitlab.com/arctic-fox/spectator/-/issues/65)
- [Compiler optimizations](https://gitlab.com/arctic-fox/spectator/-/issues/66)
- More tests!
### How to submit changes
Submit your changes as a pull request (either GitLab or GitHub).
Please keep change requests focused on a single feature or bug.
Larger pull requests may be broken up into logical pieces.
This helps reviewers digest the changes.
Describe the purpose of the change, what it addresses.
Provide links to related issues, bugs the changes fix, or external resources (i.e. RSpec docs).
Note any significant items or breaking changes.
Include a snippet of code demonstrating the code, if applicable.
Please include tests for new code and bug fixes.
Some parts of Spectator are harder to test than others.
Check out the [testing](#testing) section for more information.
### How to report a bug
Be sure you can reproduce the issue.
Try to reduce the amount of code and complexity needed to reproduce the issue.
Check of outstanding issues, it might already be reported.
Open an issue (either [GitLab](https://gitlab.com/arctic-fox/spectator/-/issues/new) or [GitHub](https://github.com/icy-arctic-fox/spectator/issues/new)).
Provide information on what you're trying to do.
Include source code that reproduces the issue.
Add any specific details or specifics about your usage and environment.
For instance: using Windows, compiling with specific flags, dependencies on other shards, custom helper code, etc.
Maintainers generally won't close an issue until they're certain it is resolved.
We may ask that you verify the fix on your end before closing the issue.
### How to request an enhancement
Check for existing feature requests, someone might already have the same idea.
Create a new issue (either [GitLab](https://gitlab.com/arctic-fox/spectator/-/issues/new) or [GitHub](https://github.com/icy-arctic-fox/spectator/issues/new)).
Explain what you're trying to do or would like improved.
Include reasoning - why do you want this feature, what would it help with?
Provide code snippets if it helps illustrate the idea.
## Where can I get help?
First checkout the [README](README.md) and wiki ([GitLab](https://gitlab.com/arctic-fox/spectator/-/wikis/home) | [GitHub](https://github.com/icy-arctic-fox/spectator/wiki)).
These two locations are official sources of information for the project.
Look for existing issues before creating a new one.
We use issues for bugs, features, and general help/support.
You might find something that addresses your issue or points you in the right direction.
Adding a :+1: to the original issue is appreciated!
If you can't find anything already out there, submit an issue.
Explain what you're trying to do and maybe an explanation of why.
Sometimes we might discover a better solution for what you're attempting to do.
Provide code snippets, command-line, and examples if possible.
## Development
Spectator is cross-platform and should work on any OS that [Crystal supports](https://crystal-lang.org/install/).
Development on Spectator is possible with a default Crystal installation (with Shards).
To get started:
1. Fork the repository [GitLab](https://gitlab.com/arctic-fox/spectator/fork/new) | [GitHub](https://github.com/icy-arctic-fox/spectator/fork)
2. Clone the forked git repository.
3. Run `shards` in the repository to pull developer dependencies.
4. Verify everything works by running `crystal spec`.
At this point, dive into the code or check out the [architecture](ARCHITECTURE.md).
### Testing
Spectator uses itself for testing.
Please try to write tests for any new features that are added.
Bugs should have tests created for them to combat regression.
The `spec/` directory contains all tests.
The feature tests are grouped into sub directories based on their type, they are:
- `docs/` - Example snippets from Spectator's documentation.
- `features/` - Tests for Spectator's DSL and headline features.
- `issues/` - Tests for reported bugs.
- `matchers/` - Exhaustive testing of matchers.
- `rspec/` - Examples from RSpec's documentation modified slightly to work with Spectator. See: https://relishapp.com/rspec/
- `spectator/` - Unit tests for Spectator internals.
The `helpers/` directory contains utilities to aid tests.
### Style guide and conventions
General Crystal styling should be used.
To ensure everything is formatted correctly, run `crystal tool format` prior to committing.
Additionally, [Ameba](https://crystal-ameba.github.io/) is used to encourage best coding practices.
To run Ameba, run `bin/ameba` in the root of the repository.
This requires that shards have been installed.
Formatting checks and Ameba will be run as part of the CI pipeline.
Both are required to pass before a pull request will be accepted and merged.
Exceptions can be made for some Ameba issues.
Adding `#ameba:disable` to a line will [disable Ameba](https://crystal-ameba.github.io/ameba/#inline-disabling) for a particular issue.
However, please prefer to fix the issue instead of ignoring them.
Please attempt to document every class and public method.
Documentation isn't required on private methods and trivial code.
Please add comments explaining algorithms and complex code.
DSL methods and macros should be heavily documented.
This helps developers using Spectator.
HTML documentation is automatically generated (by `crystal docs`) and published to [GitLab pages](https://arctic-fox.gitlab.io/spectator).
### Branching
The `master` branch contains the latest stable code.
Branches are used for features, fixes, and release preparation.
A new minor release is made whenever there is enough functionality to warrant one or some time has passed since the last one and there's pending fixes.
A new major release occurs when there are substantial changes to Spectator.
Known breaking changes are always in a major release.
Tags are made for each release.

12
LICENSE
View file

@ -1,6 +1,6 @@
The MIT License (MIT)
MIT License
Copyright (c) 2018 Michael Miller
Copyright (c) 2024 Michael Miller <icy.arctic.fox@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

426
README.md
View file

@ -1,427 +1,39 @@
Spectator
=========
# spectator
Spectator is a fully-featured spec-based test framework for Crystal.
It mimics features from [RSpec](http://rspec.info/).
Developers coming from Ruby and RSpec will feel right at home.
Spectator provides additional functionality to make testing easier and more fluent.
TODO: Write a description here
**Goal:**
## Installation
Spectator is designed to:
1. Add the dependency to your `shard.yml`:
- Reduce complexity of test code.
- Remove boilerplate from tests.
- Lower the difficulty of writing non-trivial tests.
- Provide an elegant syntax that is easy to read and understand.
- Provide common utilities that the end-user would otherwise need to write.
```yaml
dependencies:
spectator:
github: icy-arctic-fox/spectator
```
Installation
------------
2. Run `shards install`
Add this to your application's `shard.yml`:
```yaml
development_dependencies:
spectator:
gitlab: arctic-fox/spectator
version: ~> 0.12.0
```
Usage
-----
If it doesn't exist already, create a `spec/spec_helper.cr` file.
In it, place the following:
## Usage
```crystal
require "../src/*"
require "spectator"
```
This will include Spectator and the source code for your shard.
Now you can start writing your specs.
The syntax is the same as what you would expect from modern RSpec.
The "expect" syntax is recommended and the default, however the "should" syntax is also available.
Your specs must be wrapped in a `Spectator.describe` block.
All other blocks inside the top-level block may use `describe` and `context` without the `Spectator.` prefix.
TODO: Write usage instructions here
Here's a minimal spec to demonstrate:
## Development
```crystal
require "./spec_helper"
TODO: Write development instructions here
Spectator.describe String do
subject { "foo" }
## Contributing
describe "#==" do
context "with the same value" do
let(value) { subject.dup }
it "is true" do
is_expected.to eq(value)
end
end
context "with a different value" do
let(value) { "bar" }
it "is false" do
is_expected.to_not eq(value)
end
end
end
end
```
If you find yourself trying to shoehorn in functionality
or unsure how to write a test, please create an [issue](https://gitlab.com/arctic-fox/spectator/issues/new) for it.
The goal is to make it as easy as possible to write specs and keep your code clean.
We may come up with a solution or even introduce a feature to support your needs.
**NOTE:** Due to the way this shard uses macros,
you may find that some code you would expect to work, or works in other spec libraries, creates syntax errors.
If you run into this, please create an issue so that we may try to resolve it.
Features
--------
Spectator has all of the basic functionality for BDD.
For full documentation on what it can do, please visit the [wiki](https://gitlab.com/arctic-fox/spectator/wikis/home).
### Contexts
The DSL supports arbitrarily nested contexts.
Contexts can have values defined for multiple tests (`let` and `subject`).
Additionally, hooks can be used to ensure any initialization or cleanup is done (`before`, `after`, and `around`).
Pre- and post-conditions can be used to ensure code contracts are kept.
```crystal
# Initialize the database before running the tests in this context.
before_all { Database.init }
# Teardown the database and cleanup after tests in the is context finish.
after_all { Database.cleanup }
# Before each test, add some rows to the database.
let(row_count) { 5 }
before_each do
row_count.times { Database.insert_row }
end
# Remove the rows after the test to get a clean slate.
after_each { Database.clear }
describe "#row_count" do
it "returns the number of rows" do
expect(Database.row_count).to eq(row_count)
end
end
```
Spectator has different types of contexts to reduce boilerplate.
One is the `sample` context.
This context type repeats all tests (and contexts within) for a set of values.
For instance, some feature should behave the same for different input.
However, some inputs might cause problems, but should behave the same.
An example is various strings (empty strings, quoted strings, strings with non-ASCII, etc),
and numbers (positive, negative, zero, NaN, infinity).
```crystal
# List of integers to test against.
def various_integers
[-7, -1, 0, 1, 42]
end
# Repeat nested tests for every value in `#various_integers`.
sample various_integers do |int|
# Example that checks if a fictitious method `#format` converts to strings.
it "formats correctly" do
expect(format(int)).to eq(int.to_s)
end
end
```
Another context type is `provided`.
This context drastically reduces the amount of code needed in some scenarios.
It can be used where one (or more inputs) changes the output of multiple methods.
The `provided` context gives a concise syntax for this use case.
```crystal
subject(user) { User.new(age) }
# Each expression in the `provided` block is its own test.
provided age = 10 do
expect(user.can_drive?).to be_false
expect(user.can_vote?).to be_false
end
provided age = 16 do
expect(user.can_drive?).to be_true
expect(user.can_vote?).to be_false
end
provided age = 18 do
expect(user.can_drive?).to be_true
expect(user.can_vote?).to be_true
end
```
### Assertions
Spectator supports two formats for assertions (expectations).
The preferred format is the "expect syntax".
This takes the form:
```crystal
expect(THIS).to eq(THAT)
```
The other format, "should syntax" is used by Crystal's default Spec.
```
THIS.should eq(THAT)
```
The first format doesn't monkey-patch the `Object` type.
And as a bonus, it captures the expression or variable passed to `expect()`.
For instance, compare these two tests:
```crystal
foo = "Hello world"
foo.size.should eq(12) # Wrong on purpose!
```
Produces this error output:
```text
Failure: 11 does not equal 12
expected: 11
actual: 12
```
Which is reasonable, but where did 11 come from?
Alternatively, with the "expect syntax":
```crystal
foo = "Hello world"
expect(foo.size).to eq(12) # Wrong on purpose!
```
Produces this error output:
```text
Failure: foo.size does not equal 12
expected: 12
actual: 11
```
This makes it clearer what was being tested and failed.
### Matchers
Spectator has a variety of matchers for assertions.
These are named in such a way to help tests read as plain English.
Matchers can be used on any value or block.
There are typical matchers for testing equality: `eq` and `ne`.
And matchers for comparison: `<`, `<=`, `>`, `>=`, `be_within`.
There are matchers for checking contents of collections:
`contain`, `have`, `start_with`, `end_with`, `be_empty`, `have_key`, and more.
See the [wiki](https://gitlab.com/arctic-fox/spectator/wikis/Matchers) for a full list of matchers.
### Running
Spectator supports multiple options for running tests.
"Fail fast" aborts on the first test failure.
"Fail blank" fails if there are no tests.
Tests can be filtered by their location and name.
Additionally, tests can be randomized.
Spectator can be configured with command-line arguments,
a configure block in a `spec_helper.cr` file, and `.spectator` configuration file.
```crystal
Spectator.configure do |config|
config.fail_blank # Fail on no tests.
config.randomize # Randomize test order.
config.profile # Display slowest tests.
end
```
### Mocks and Doubles
Spectator supports an extensive mocking feature set via two types - mocks and doubles.
Mocks are used to override behavior in existing types.
Doubles are objects that stand-in when there are no type restrictions.
Stubs can be defined on both which control how methods behave.
```crystal
abstract class Interface
abstract def invoke(thing) : String
end
# Type being tested.
class Driver
def do_something(interface : Interface, thing)
interface.invoke(thing)
end
end
Spectator.describe Driver do
# Define a mock for Interface.
mock Interface
# Define a double that the interface will use.
double(:my_double, foo: 42)
it "does a thing" do
# Create an instance of the mock interface.
interface = mock(Interface)
# Indicate that `#invoke` should return "test" when called.
allow(interface).to receive(:invoke).and_return("test")
# Create an instance of the double.
dbl = double(:my_double)
# Call the mock method.
subject.do_something(interface, dbl)
# Verify everything went okay.
expect(interface).to have_received(:invoke).with(dbl)
end
end
```
For details on mocks and doubles, see the [wiki](https://gitlab.com/arctic-fox/spectator/-/wikis/Mocks-and-Doubles).
### Output
Spectator matches Crystal's default Spec output with some minor changes.
JUnit and TAP are also supported output formats.
There are also highly detailed JSON and HTML outputs.
Development
-----------
This shard is still in active development.
New features are being added and existing functionality improved.
Spectator is well-tested, but may have some yet-to-be-found bugs.
### Feature Progress
In no particular order, features that have been implemented and are planned.
Items not marked as completed may have partial implementations.
- [ ] DSL
- [X] `describe` and `context` blocks
- [X] Contextual values with `let`, `let!`, `subject`, `described_class`
- [X] Test multiple and generated values - `sample`, `random_sample`
- [X] Concise syntax - `provided` (was the now deprecated `given`)
- [X] Before and after hooks - `before_each`, `before_all`, `after_each`, `after_all`, `around_each`
- [X] Pre- and post-conditions - `pre_condition`, `post_condition`
- [ ] Other hooks - `on_success`, `on_failure`, `on_error`
- [X] One-liner syntax
- [X] Should syntax - `should`, `should_not`
- [X] Helper methods and modules
- [ ] Aliasing - custom example group types with preset attributes
- [X] Pending tests - `pending`
- [ ] Shared examples - `behaves_like`, `include_examples`
- [X] Deferred expectations - `to_eventually`, `to_never`
- [ ] Matchers
- [X] Equality matchers - `eq`, `ne`, `be ==`, `be !=`
- [X] Comparison matchers - `be <`, `be <=`, `be >`, `be >=`, `be_within[.of]`, `be_close`
- [X] Type matchers - `be_a`, `respond_to`
- [ ] Collection matchers
- [X] `contain`
- [X] `have`
- [X] `contain_exactly`
- [X] `contain_exactly.in_any_order`
- [X] `match_array`
- [X] `match_array.in_any_order`
- [X] `start_with`
- [X] `end_with`
- [X] `be_empty`
- [X] `have_key`
- [X] `have_value`
- [X] `all`
- [ ] `all_satisfy`
- [X] Truthy matchers - `be`, `be_true`, `be_truthy`, `be_false`, `be_falsey`, `be_nil`
- [X] Error matchers - `raise_error`
- [ ] Yield matchers - `yield_control[.times]`, `yield_with_args[.times]`, `yield_with_no_args[.times]`, `yield_successive_args`
- [ ] Output matchers - `output[.to_stdout|.to_stderr]`
- [X] Predicate matchers - `be_x`, `have_x`
- [ ] Misc. matchers
- [X] `match`
- [ ] `satisfy`
- [X] `change[.by|.from[.to]|.to|.by_at_least|.by_at_most]`
- [X] `have_attributes`
- [ ] Compound - `and`, `or`
- [ ] Mocks and Doubles
- [X] Mocks (Stub real types) - `mock TYPE { }`
- [X] Doubles (Stand-ins for real types) - `double NAME { }`
- [X] Method stubs - `allow().to receive()`, `allow().to receive().and_return()`
- [X] Spies - `expect().to have_received()`
- [X] Message expectations - `expect().to have_received().at_least()`
- [X] Argument expectations - `expect().to have_received().with()`
- [ ] Message ordering - `expect().to have_received().ordered`
- [X] Null doubles
- [X] Runner
- [X] Fail fast
- [X] Test filtering - by name, context, and tags
- [X] Fail on no tests
- [X] Randomize test order
- [X] Dry run - for validation and checking formatted output
- [X] Config block in `spec_helper.cr`
- [X] Config file - `.spectator`
- [X] Reporter and formatting
- [X] RSpec/Crystal Spec default
- [X] JSON
- [X] JUnit
- [X] TAP
- [X] HTML
### How it Works (in a nutshell)
This shard makes extensive use of the Crystal macro system to build classes and modules.
Each `describe` and `context` block creates a new class that inherits its parent.
The `it` block creates an method.
An instance of the group class is created to run the test.
Each group class includes all test values and hooks.
Contributing
------------
1. Fork it (GitHub <https://github.com/icy-arctic-fox/spectator/fork> or GitLab <https://gitlab.com/arctic-fox/spectator/fork/new>)
1. Fork it (<https://github.com/icy-arctic-fox/spectator/fork>)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull/Merge Request
5. Create a new Pull Request
Please make sure to run `crystal tool format` before submitting.
The CI build checks for properly formatted code.
[Ameba](https://crystal-ameba.github.io/) is run to check for code style.
## Contributors
Documentation is automatically generated and published to GitLab pages.
It can be found here: https://arctic-fox.gitlab.io/spectator
This project's home is (and primarily developed) on [GitLab](https://gitlab.com/arctic-fox/spectator).
A mirror is maintained to [GitHub](https://github.com/icy-arctic-fox/spectator).
Issues, pull requests (merge requests), and discussion are welcome on both.
Maintainers will ensure your contributions make it in.
For more information, see: [CONTRIBUTING.md](CONTRIBUTING.md)
### Testing
Tests must be written for any new functionality.
The `spec/` directory contains feature tests as well as unit tests.
These demonstrate small bits of functionality.
The feature tests are grouped into sub directories based on their type, they are:
- docs/ - Example snippets from Spectator's documentation.
- rspec/ - Examples from RSpec's documentation modified slightly to work with Spectator.
See: https://relishapp.com/rspec/
Additional sub directories in this directory represent the modules/projects of RSpec.
The other directories are for unit testing various parts of Spectator.
- [Michael Miller](https://github.com/icy-arctic-fox) - creator and maintainer

View file

@ -1,16 +1,11 @@
name: spectator
version: 0.12.0
version: 0.14.0
description: |
Feature-rich testing framework for Crystal inspired by RSpec.
authors:
- Michael Miller <icy.arctic.fox@gmail.com>
crystal: 1.6.0
crystal: ">= 1.6.0, < 2.0.0"
license: MIT
development_dependencies:
ameba:
github: crystal-ameba/ameba
version: ~> 1.2.0

View file

@ -1,15 +0,0 @@
require "../spec_helper"
# https://gitlab.com/arctic-fox/spectator/-/wikis/Anonymous-Doubles
Spectator.describe "Anonymous Doubles Docs" do
it "does something" do
dbl = double(foo: 42)
expect(dbl.foo).to eq(42)
end
it "does something" do
dbl = double(foo: 42)
allow(dbl).to receive(:foo).and_return(123)
expect(dbl.foo).to eq(123)
end
end

View file

@ -1,91 +0,0 @@
require "../spec_helper"
# https://gitlab.com/arctic-fox/spectator/-/wikis/Custom-Matchers
Spectator.describe "Custom Matchers Docs" do
context "value matcher" do
# Sub-type of Matcher to suit our needs.
# Notice this is a struct.
struct MultipleOfMatcher(ExpectedType) < Spectator::Matchers::ValueMatcher(ExpectedType)
# Short text about the matcher's purpose.
# This explains what condition satisfies the matcher.
# The description is used when the one-liner syntax is used.
def description : String
"is a multiple of #{expected.label}"
end
# Checks whether the matcher is satisfied with the expression given to it.
private def match?(actual : Spectator::Expression(T)) : Bool forall T
actual.value % expected.value == 0
end
# Message displayed when the matcher isn't satisfied.
# The message should typically only contain the test expression labels.
private def failure_message(actual : Spectator::Expression(T)) : String forall T
"#{actual.label} is not a multiple of #{expected.label}"
end
# Message displayed when the matcher isn't satisfied and is negated.
# This is essentially what would satisfy the matcher if it wasn't negated.
# The message should typically only contain the test expression labels.
private def failure_message_when_negated(actual : Spectator::Expression(T)) : String forall T
"#{actual.label} is a multiple of #{expected.label}"
end
end
# The DSL portion of the matcher.
# This captures the test expression and creates an instance of the matcher.
macro be_a_multiple_of(expected)
%value = ::Spectator::Value.new({{expected}}, {{expected.stringify}})
MultipleOfMatcher.new(%value)
end
specify do
expect(9).to be_a_multiple_of(3)
# or negated:
expect(5).to_not be_a_multiple_of(2)
end
specify "failure messages" do
expect { expect(9).to be_a_multiple_of(5) }.to raise_error(Spectator::ExpectationFailed, "9 is not a multiple of 5")
expect { expect(6).to_not be_a_multiple_of(3) }.to raise_error(Spectator::ExpectationFailed, "6 is a multiple of 3")
end
end
context "standard matcher" do
struct OddMatcher < Spectator::Matchers::StandardMatcher
def description : String
"is odd"
end
private def match?(actual : Spectator::Expression(T)) : Bool forall T
actual.value % 2 == 1
end
private def failure_message(actual : Spectator::Expression(T)) : String forall T
"#{actual.label} is not odd"
end
private def failure_message_when_negated(actual : Spectator::Expression(T)) : String forall T
"#{actual.label} is odd"
end
private def does_not_match?(actual : Spectator::Expression(T)) : Bool forall T
actual.value % 2 == 0
end
end
macro be_odd
OddMatcher.new
end
specify do
expect(9).to be_odd
expect(2).to_not be_odd
end
specify "failure messages" do
expect { expect(2).to be_odd }.to raise_error(Spectator::ExpectationFailed, "2 is not odd")
expect { expect(3).to_not be_odd }.to raise_error(Spectator::ExpectationFailed, "3 is odd")
end
end
end

View file

@ -1,79 +0,0 @@
require "../spec_helper"
# https://gitlab.com/arctic-fox/spectator/-/wikis/Doubles
Spectator.describe "Doubles Docs" do
let(answer) { 42 }
double :my_double, answer: 5 do
def answer(arg1, arg2)
arg1 + arg2
end
end
it "does something" do
dbl = double(:my_double, answer: answer)
expect(dbl.answer).to eq(42)
expect(dbl.answer(1, 2)).to eq(3)
end
class Emitter
def initialize(@value : Int32)
end
def emit(target)
target.call(@value)
end
end
context "Expecting Behavior" do
describe Emitter do
subject { Emitter.new(42) }
double :target, call: nil
describe "#emit" do
it "invokes #call on the target" do
target = double(:target)
subject.emit(target)
expect(target).to have_received(:call).with(42)
end
end
end
it "does something" do
dbl = double(:my_double)
allow(dbl).to receive(:answer).and_return(42) # Merge this line...
dbl.answer
expect(dbl).to have_received(:answer) # and this line.
end
it "does something" do
dbl = double(:my_double)
expect(dbl).to receive(:answer).and_return(42)
dbl.answer
end
end
context "Class Doubles" do
double :my_double do
# Define class methods with `self.` prefix.
stub def self.something
42
end
end
it "does something" do
# Default stubs can be defined with key-value pairs (keyword arguments).
dbl = class_double(:my_double, something: 3)
expect(dbl.something).to eq(3)
# Stubs can be changed with `allow`.
allow(dbl).to receive(:something).and_return(5)
expect(dbl.something).to eq(5)
# Even the expect-receive syntax works.
expect(dbl).to receive(:something).and_return(7)
dbl.something
end
end
end

View file

@ -1,23 +0,0 @@
require "../spec_helper"
Spectator.describe String do
subject { "foo" }
describe "#==" do
context "with the same value" do
let(value) { subject.dup }
it "is true" do
is_expected.to eq(value)
end
end
context "with a different value" do
let(value) { "bar" }
it "is false" do
is_expected.to_not eq(value)
end
end
end
end

View file

@ -1,60 +0,0 @@
require "../spec_helper"
Spectator.describe String do
# This is a helper method.
def random_string(length)
chars = ('a'..'z').to_a
String.build(length) do |builder|
length.times { builder << chars.sample }
end
end
describe "#size" do
subject { random_string(10).size }
it "is the length of the string" do
is_expected.to eq(10)
end
end
end
Spectator.describe String do
# length is now pulled from value defined by `let`.
def random_string
chars = ('a'..'z').to_a
String.build(length) do |builder|
length.times { builder << chars.sample }
end
end
describe "#size" do
let(length) { 10 } # random_string uses this.
subject { random_string.size }
it "is the length of the string" do
is_expected.to eq(length)
end
end
end
module StringHelpers
def random_string
chars = ('a'..'z').to_a
String.build(length) do |builder|
length.times { builder << chars.sample }
end
end
end
Spectator.describe String do
include StringHelpers
describe "#size" do
let(length) { 10 }
subject { random_string.size }
it "is the length of the string" do
is_expected.to eq(length)
end
end
end

View file

@ -1,257 +0,0 @@
require "../spec_helper"
# https://gitlab.com/arctic-fox/spectator/-/wikis/Mocks
Spectator.describe "Mocks Docs" do
context "Abstract Types" do
abstract class MyClass
abstract def something : String
end
mock MyClass
it "does something" do
mock = mock(MyClass)
allow(mock).to receive(:something).and_return("test") # Uncomment this line to fix.
mock.something
end
end
abstract class MyClass
abstract def answer : Int32
abstract def answer(arg1, arg2) : Int32
end
mock MyClass, answer: 5 do
def answer(arg1, arg2) : Int32
arg1 + arg2
end
end
let(answer) { 42 }
it "does something" do
mock = mock(MyClass, answer: answer)
expect(mock.answer).to eq(42)
expect(mock.answer(1, 2)).to eq(3)
end
context "Instance Variables and Initializers" do
class MyClass
def initialize(@value : Int32)
end
end
mock MyClass do
def initialize(@value : Int32 = 0) # Note the lack of `stub` here.
end
end
it "can create a mock" do
mock = mock(MyClass)
expect(mock).to_not be_nil
end
end
context "Expecting Behavior" do
abstract class Target
abstract def call(value) : Nil
end
class Emitter
def initialize(@value : Int32)
end
def emit(target : Target)
target.call(@value)
end
end
describe Emitter do
subject { Emitter.new(42) }
mock Target, call: nil
describe "#emit" do
it "invokes #call on the target" do
target = mock(Target)
subject.emit(target)
expect(target).to have_received(:call).with(42)
end
end
end
it "does something" do
mock = mock(MyClass)
allow(mock).to receive(:answer).and_return(42) # Merge this line...
mock.answer
expect(mock).to have_received(:answer) # and this line.
end
it "does something" do
mock = mock(MyClass)
expect(mock).to receive(:answer).and_return(42)
mock.answer
end
end
context "Class Mocks" do
class MyClass
def self.something
0
end
end
mock MyClass do
# Define class methods with `self.` prefix.
stub def self.something
42
end
end
it "does something" do
# Default stubs can be defined with key-value pairs (keyword arguments).
mock = class_mock(MyClass, something: 3)
expect(mock.something).to eq(3)
# Stubs can be changed with `allow`.
allow(mock).to receive(:something).and_return(5)
expect(mock.something).to eq(5)
# Even the expect-receive syntax works.
expect(mock).to receive(:something).and_return(7)
mock.something
end
end
context "Mock Modules" do
module MyModule
def something
# ...
end
end
describe "#something" do
# Define a mock for MyModule.
mock MyClass
it "does something" do
# Use mock here.
end
end
module MyFileUtils
def self.rm_rf(path)
# ...
end
end
mock MyFileUtils
it "deletes all of my files" do
utils = class_mock(MyFileUtils)
allow(utils).to receive(:rm_rf)
utils.rm_rf("/")
expect(utils).to have_received(:rm_rf).with("/")
end
module MyFileUtils2
extend self
def rm_rf(path)
# ...
end
end
mock(MyFileUtils2) do
# Define a default stub for the method.
stub def self.rm_rf(path)
# ...
end
end
it "deletes all of my files part 2" do
utils = class_mock(MyFileUtils2)
allow(utils).to receive(:rm_rf)
utils.rm_rf("/")
expect(utils).to have_received(:rm_rf).with("/")
end
module Runnable
def run
# ...
end
end
mock Runnable
specify do
runnable = mock(Runnable) # or new_mock(Runnable)
runnable.run
end
module Runnable2
abstract def command : String
def run_one
"Running #{command}"
end
end
mock Runnable2, command: "ls -l"
specify do
runnable = mock(Runnable2)
expect(runnable.run_one).to eq("Running ls -l")
runnable = mock(Runnable2, command: "echo foo")
expect(runnable.run_one).to eq("Running echo foo")
end
context "Injecting Mocks" do
module MyFileUtils
def self.rm_rf(path)
true
end
end
inject_mock MyFileUtils do
stub def self.rm_rf(path)
"Simulating deletion of #{path}"
false
end
end
specify do
expect(MyFileUtils.rm_rf("/")).to be_false
end
end
end
context "Injecting Mocks" do
struct MyStruct
def something
42
end
def something_else(arg1, arg2)
"#{arg1} #{arg2}"
end
end
inject_mock MyStruct, something: 5 do
stub def something_else(arg1, arg2)
"foo bar"
end
end
specify "creating a mocked type without `mock`" do
inst = MyStruct.new
expect(inst).to receive(:something).and_return(7)
inst.something
end
it "reverts to default stub for other examples" do
inst = mock(MyStruct)
expect(inst.something).to eq(5) # Default stub used instead of original behavior.
end
end
end

View file

@ -1,16 +0,0 @@
require "../spec_helper"
# https://gitlab.com/arctic-fox/spectator/-/wikis/Null-Objects
Spectator.describe "Null Objects Docs" do
double :my_double
it "returns itself for undefined methods" do
dbl = double(:my_double).as_null_object
expect(dbl.some_undefined_method).to be(dbl)
end
it "can be used to chain methods" do
dbl = double(:my_double).as_null_object
expect(dbl.foo.bar.baz).to be(dbl)
end
end

View file

@ -1,36 +0,0 @@
require "../spec_helper"
module Readme
abstract class Interface
abstract def invoke(thing) : String
end
# Type being tested.
class Driver
def do_something(interface : Interface, thing)
interface.invoke(thing)
end
end
end
Spectator.describe Readme::Driver do
# Define a mock for Interface.
mock Readme::Interface
# Define a double that the interface will use.
double(:my_double, foo: 42)
it "does a thing" do
# Create an instance of the mock interface.
interface = mock(Readme::Interface)
# Indicate that `#invoke` should return "test" when called.
allow(interface).to receive(:invoke).and_return("test")
# Create an instance of the double.
dbl = double(:my_double)
# Call the mock method.
subject.do_something(interface, dbl)
# Verify everything went okay.
expect(interface).to have_received(:invoke).with(dbl)
end
end

View file

@ -1,34 +0,0 @@
require "../spec_helper"
Spectator.describe String do
let(normal_string) { "foobar" }
let(empty_string) { "" }
describe "#empty?" do
subject { string.empty? }
context "when empty" do
let(string) { empty_string }
it "is true" do
is_expected.to be_true
end
end
context "when not empty" do
let(string) { normal_string }
it "is false" do
is_expected.to be_false
end
end
end
end
Spectator.describe Bytes do
it "stores an array of bytes" do
bytes = Bytes.new(32)
bytes[0] = 42
expect(bytes[0]).to eq(42)
end
end

View file

@ -1,130 +0,0 @@
require "../spec_helper"
# https://gitlab.com/arctic-fox/spectator/-/wikis/Stubs
Spectator.describe "Stubs Docs" do
double :time_double, time_in: Time.utc(2016, 2, 15, 10, 20, 30)
double :my_double, something: 42, answer?: false
let(dbl) { double(:my_double) }
def receive_time_in_utc
receive(:time_in).with(:utc).and_return(Time.utc)
end
it "returns the time in UTC" do
dbl = double(:time_double)
allow(dbl).to receive_time_in_utc
expect(dbl.time_in(:utc).zone.name).to eq("UTC")
end
context "Modifiers" do
double :my_double, something: 42, answer?: false
let(dbl) { double(:my_double) }
context "and_return" do
specify do
allow(dbl).to receive(:something).and_return(42)
expect(dbl.something).to eq(42)
end
specify do
allow(dbl).to receive(:something).and_return(1, 2, 3)
expect(dbl.something).to eq(1)
expect(dbl.something).to eq(2)
expect(dbl.something).to eq(3)
expect(dbl.something).to eq(3)
end
end
context "and_raise" do
specify do
allow(dbl).to receive(:something).and_raise # Raise `Exception` with no message.
expect { dbl.something }.to raise_error(Exception)
allow(dbl).to receive(:something).and_raise(IO::Error) # Raise `IO::Error` with no message.
expect { dbl.something }.to raise_error(IO::Error)
allow(dbl).to receive(:something).and_raise(KeyError, "Missing key: :foo") # Raise `KeyError` with the specified message.
expect { dbl.something }.to raise_error(KeyError, "Missing key: :foo")
exception = ArgumentError.new("Malformed")
allow(dbl).to receive(:something).and_raise(exception) # Raise `exception`.
expect { dbl.something }.to raise_error(ArgumentError, "Malformed")
end
end
context "with" do
specify do
allow(dbl).to receive(:answer?).and_return(false)
allow(dbl).to receive(:answer?).with(42).and_return(true)
expect(dbl.answer?(42)).to be_true
expect(dbl.answer?(5)).to be_false
end
specify do
allow(dbl).to receive(:answer?).with(Int, key: /foo/).and_return(true)
expect(dbl.answer?(42, key: "foobar")).to be_true
end
end
end
context "Expect-Receive Syntax" do
class Driver
def doit(thing)
thing.call
end
end
describe Driver do
describe "#doit" do
double :thing, call: 5
it "calls thing.call (1)" do
thing = double(:thing)
allow(thing).to receive(:call).and_return(42)
subject.doit(thing)
expect(thing).to have_received(:call)
end
it "calls thing.call (2)" do
thing = double(:thing)
expect(thing).to receive(:call).and_return(42)
subject.doit(thing)
end
it "calls thing.call (3)" do
thing = double(:thing)
allow(thing).to receive(:call).and_return(42)
expect(thing).to_eventually have_received(:call)
subject.doit(thing)
end
end
end
specify do
expect(dbl).to receive(:answer?).with(42).and_return(true)
dbl.answer?(42)
end
end
context "Default Stubs" do
double :my_double, foo: "foo" do # Default stub for #foo
# Default stub for #bar
stub def bar
"bar"
end
end
it "does something" do
dbl = double(:my_double)
expect(dbl.foo).to eq("foo")
expect(dbl.bar).to eq("bar")
# Overriding initial defaults.
dbl = double(:my_double, foo: "FOO", bar: "BAR")
expect(dbl.foo).to eq("FOO")
expect(dbl.bar).to eq("BAR")
end
end
end

View file

@ -1,32 +0,0 @@
require "../spec_helper"
Spectator.describe "subject" do
subject(array1) { [1, 2, 3] }
subject(array2) { [4, 5, 6] }
it "has different elements" do
expect(array1).to_not eq(subject) # array2 would also work here.
end
let(string) { "foobar" }
it "isn't empty" do
expect(string.empty?).to be_false
end
it "is six characters" do
expect(string.size).to eq(6)
end
let(array) { [0, 1, 2] }
it "modifies the array" do
array[0] = 42
expect(array).to eq([42, 1, 2])
end
it "doesn't carry across tests" do
array[1] = 777
expect(array).to eq([0, 777, 2])
end
end

View file

@ -1,41 +0,0 @@
require "../spec_helper"
Spectator.describe Spectator, :smoke do
describe "aggregate_failures" do
it "captures multiple failed expectations" do
expect do
aggregate_failures do
expect(true).to be_false
expect(false).to be_true
end
end.to raise_error(Spectator::MultipleExpectationsFailed, /2 failures/)
end
it "raises normally for one failed expectation" do
expect do
aggregate_failures do
expect(true).to be_false
expect(true).to be_true
end
end.to raise_error(Spectator::ExpectationFailed)
end
it "doesn't raise when there are no failed expectations" do
expect do
aggregate_failures do
expect(false).to be_false
expect(true).to be_true
end
end.to_not raise_error(Spectator::ExpectationFailed)
end
it "supports naming the block" do
expect do
aggregate_failures "contradiction" do
expect(true).to be_false
expect(false).to be_true
end
end.to raise_error(Spectator::MultipleExpectationsFailed, /contradiction/)
end
end
end

View file

@ -1,55 +0,0 @@
require "../spec_helper"
Spectator.describe Spectator, :smoke do
context "consice syntax" do
describe "provided group with a single assignment" do
provided x = 42 do
expect(x).to eq(42)
end
end
describe "provided group with multiple assignments" do
provided x = 42, y = 123 do
expect(x).to eq(42)
expect(y).to eq(123)
end
end
describe "provided group with a single named argument" do
provided x: 42 do
expect(x).to eq(42)
end
end
describe "provided group with multiple named arguments" do
provided x: 42, y: 123 do
expect(x).to eq(42)
expect(y).to eq(123)
end
end
describe "provided group with mix of assignments and named arguments" do
provided x = 42, y: 123 do
expect(x).to eq(42)
expect(y).to eq(123)
end
provided x = 42, y = 123, z: 0, foo: "bar" do
expect(x).to eq(42)
expect(y).to eq(123)
expect(z).to eq(0)
expect(foo).to eq("bar")
end
end
describe "provided group with references to other arguments" do
let(foo) { "bar" }
provided x = 3, y: x * 5, baz: foo.sub('r', 'z') do
expect(x).to eq(3)
expect(y).to eq(15)
expect(baz).to eq("baz")
end
end
end
end

View file

@ -1,31 +0,0 @@
require "../spec_helper"
Spectator.describe Spectator, :smoke do
it "supports custom expectation messages" do
expect do
expect(false).to be_true, "paradox!"
end.to raise_error(Spectator::ExampleFailed, "paradox!")
end
it "supports custom expectation messages with a proc" do
count = 0
expect do
expect(false).to be_true, ->{ count += 1; "Failed #{count} times" }
end.to raise_error(Spectator::ExampleFailed, "Failed 1 times")
end
context "not_to" do
it "supports custom expectation messages" do
expect do
expect(true).not_to be_true, "paradox!"
end.to raise_error(Spectator::ExampleFailed, "paradox!")
end
it "supports custom expectation messages with a proc" do
count = 0
expect do
expect(true).not_to be_true, ->{ count += 1; "Failed #{count} times" }
end.to raise_error(Spectator::ExampleFailed, "Failed 1 times")
end
end
end

View file

@ -1,70 +0,0 @@
require "../spec_helper"
Spectator.describe "Expect Type", :smoke do
context "with expect syntax" do
it "ensures a type is cast" do
value = 42.as(String | Int32)
expect(value).to be_a(String | Int32)
expect(value).to compile_as(String | Int32)
value = expect(value).to be_a(Int32)
expect(value).to eq(42)
expect(value).to be_a(Int32)
expect(value).to compile_as(Int32)
expect(value).to_not respond_to(:downcase)
end
it "ensures a type is not nil" do
value = 42.as(Int32?)
expect(value).to be_a(Int32?)
expect(value).to compile_as(Int32?)
value = expect(value).to_not be_nil
expect(value).to eq(42)
expect(value).to be_a(Int32)
expect(value).to compile_as(Int32)
expect { value.not_nil! }.to_not raise_error(NilAssertionError)
end
it "removes types from a union" do
value = 42.as(String | Int32)
expect(value).to be_a(String | Int32)
expect(value).to compile_as(String | Int32)
value = expect(value).to_not be_a(String)
expect(value).to eq(42)
expect(value).to be_a(Int32)
expect(value).to compile_as(Int32)
expect(value).to_not respond_to(:downcase)
end
end
context "with should syntax" do
it "ensures a type is cast" do
value = 42.as(String | Int32)
value.should be_a(String | Int32)
value = value.should be_a(Int32)
value.should eq(42)
value.should be_a(Int32)
value.should compile_as(Int32)
value.should_not respond_to(:downcase)
end
it "ensures a type is not nil" do
value = 42.as(Int32?)
value.should be_a(Int32?)
value = value.should_not be_nil
value.should eq(42)
value.should be_a(Int32)
value.should compile_as(Int32)
expect { value.not_nil! }.to_not raise_error(NilAssertionError)
end
it "removes types from a union" do
value = 42.as(String | Int32)
value.should be_a(String | Int32)
value = value.should_not be_a(String)
value.should eq(42)
value.should be_a(Int32)
value.should compile_as(Int32)
value.should_not respond_to(:downcase)
end
end
end

View file

@ -1,22 +0,0 @@
require "../spec_helper"
Spectator.describe "Interpolated Label", :smoke do
let(foo) { "example" }
let(bar) { "context" }
it "interpolates #{foo} labels" do |example|
expect(example.name).to eq("interpolates example labels")
end
context "within a #{bar}" do
let(foo) { "multiple" }
it "interpolates context labels" do |example|
expect(example.group.name).to eq("within a context")
end
it "interpolates #{foo} levels" do |example|
expect(example.name).to eq("interpolates multiple levels")
end
end
end

View file

@ -1,32 +0,0 @@
require "../spec_helper"
Spectator.describe Spectator, :smoke do
let(current_example) { ::Spectator::Example.current }
subject(location) { current_example.location }
context "line numbers" do
it "contains starting line of spec" do
expect(location.line).to eq(__LINE__ - 1)
end
it "contains ending line of spec" do
expect(location.end_line).to eq(__LINE__ + 1)
end
it "handles multiple lines and examples" do
# Offset is important.
expect(location.line).to eq(__LINE__ - 2)
# This line fails, refer to https://github.com/crystal-lang/crystal/issues/10562
# expect(location.end_line).to eq(__LINE__ + 2)
# Offset is still important.
end
end
context "file names" do
subject { location.file }
it "match source code" do
is_expected.to eq(__FILE__)
end
end
end

View file

@ -1,29 +0,0 @@
require "../spec_helper"
Spectator.describe "Spec metadata", :smoke do
let(interpolation) { "string interpolation" }
it "supports #{interpolation}" do |example|
expect(example.name).to eq("supports string interpolation")
end
def self.various_strings
%w[foo bar baz]
end
sample various_strings do |string|
it "works with #{string}" do |example|
expect(example.name).to eq("works with #{string}")
end
end
def self.a_hash
{"foo" => 42, "bar" => 123, "baz" => 7}
end
sample a_hash do |key, value|
it "works with #{key} = #{value}" do |example|
expect(example.name).to eq("works with #{key} = #{value}")
end
end
end

View file

@ -1,23 +0,0 @@
require "../spec_helper"
class Base; end
module SomeModule; end
Spectator.describe "Subject", :smoke do
subject { Base.new }
context "nested" do
it "inherits the parent explicit subject" do
expect(subject).to be_a(Base)
end
end
context "module" do
describe SomeModule do
it "sets the implicit subject to the module" do
expect(subject).to be(SomeModule)
end
end
end
end

View file

View file

@ -1,17 +0,0 @@
require "../spec_helper"
Spectator.describe "GitHub Issue #28" do
class Test
def foo
42
end
end
mock Test
it "matches method stubs with no_args" do
test = mock(Test)
expect(test).to receive(:foo).with(no_args).and_return(42)
test.foo
end
end

View file

@ -1,29 +0,0 @@
require "../spec_helper"
Spectator.describe "GitHub Issue #29" do
class SomeClass
def goodbye
exit 0
end
end
describe SomeClass do
it "captures exit" do
expect { subject.goodbye }.to raise_error(Spectator::SystemExit)
end
end
describe "class method" do
class Foo
def self.test
exit 0
end
end
subject { Foo }
it "must capture exit" do
expect { subject.test }.to raise_error(Spectator::SystemExit)
end
end
end

View file

@ -1,9 +0,0 @@
require "../spec_helper"
Spectator.describe "GitHub Issue #30" do
let(dbl) { double(:foo) }
it "supports block-less symbol doubles" do
expect(dbl).to_not be_nil
end
end

View file

@ -1,34 +0,0 @@
require "../spec_helper"
Spectator.describe "GitHub Issue #32" do
module TestFoo
class TestClass
def initialize
end
# the method we are testing
def self.test
new().test
end
# the method we want to ensure gets called
def test
end
end
end
let(test_class) { TestFoo::TestClass }
let(test_instance) { test_class.new }
describe "something else" do
inject_mock TestFoo::TestClass
it "must test when new is called" do
expect(test_class).to receive(:new).with(no_args).and_return(test_instance)
expect(test_instance).to receive(:test)
expect(test_class.new).to be(test_instance)
test_class.test
end
end
end

View file

@ -1,26 +0,0 @@
require "../spec_helper"
Spectator.describe "GitHub Issue #33" do
class Test
def method2
end
def method1
method2
end
end
mock Test
describe Test do
subject { mock(Test) }
describe "#method1" do
it do
expect(subject).to receive(:method2)
subject.method1
end
end
end
end

View file

@ -1,30 +0,0 @@
require "../spec_helper"
Spectator.describe "GitHub Issue #41" do
sample [1, 2, 3] do |i|
it "is itself" do
expect(i).to eq i
end
end
def self.an_array
[1, 2, 3]
end
sample an_array do |i|
it "is itself" do
expect(i).to eq(i)
end
end
# NOTE: NamedTuple does not work, must be Enumerable(T) for `sample`.
def self.a_hash
{:a => "a", :b => "b", :c => "c"}
end
sample a_hash do |k, v|
it "works on hashes" do
expect(v).to eq(k.to_s)
end
end
end

View file

@ -1,41 +0,0 @@
require "../spec_helper"
abstract class SdkInterface
abstract def register_hook(name, &block)
end
class Example
def initialize(@sdk : Sdk)
end
def configure
@sdk.register_hook("name") do
nil
end
end
end
class Sdk < SdkInterface
def initialize
end
def register_hook(name, &block)
nil
end
end
Spectator.describe Example do
mock Sdk
describe "#configure" do
it "registers a block on configure" do
sdk = mock(Sdk)
example_class = Example.new(sdk)
allow(sdk).to receive(register_hook())
example_class.configure
expect(sdk).to have_received(register_hook()).with("name")
end
end
end

View file

@ -1,51 +0,0 @@
require "../spec_helper"
class Person
def initialize(@dog = Dog.new)
end
def pet
@dog.pet
end
def pet_more
@dog.pet(5)
end
end
class Dog
def initialize
end
def pet(times = 2)
"woof" * times
end
end
Spectator.describe Person do
mock Dog
describe "#pet" do
it "pets the persons dog" do
dog = mock(Dog)
person = Person.new(dog)
allow(dog).to receive(pet()).and_return("woof")
person.pet
expect(dog).to have_received(pet()).with(2)
end
end
describe "#pet_more" do
it "pets the persons dog alot" do
dog = mock(Dog)
person = Person.new(dog)
allow(dog).to receive(pet()).and_return("woof")
person.pet_more
expect(dog).to have_received(pet()).with(5)
end
end
end

View file

@ -1,40 +0,0 @@
require "../spec_helper"
Spectator.describe "GitHub Issue #44" do
inject_mock Process do
# Instance variable that can be nil, provide a default.
@process_info = Crystal::System::Process.new(0)
end
let(command) { "ls -l" }
let(exception) { File::NotFoundError.new("File not found", file: "test.file") }
context "with positional arguments" do
before_each do
pipe = Process::Redirect::Pipe
expect(Process).to receive(:run).with(command, nil, nil, false, true, pipe, pipe, pipe, nil).and_raise(exception)
end
it "must stub Process.run" do
expect do
Process.run(command, shell: true, output: :pipe) do |_process|
end
end.to raise_error(File::NotFoundError, "File not found")
end
end
# Original issue uses keyword arguments in place of positional arguments.
context "keyword arguments in place of positional arguments" do
before_each do
pipe = Process::Redirect::Pipe
expect(Process).to receive(:run).with(command, shell: true, output: pipe).and_raise(exception)
end
it "must stub Process.run" do
expect do
Process.run(command, shell: true, output: :pipe) do |_process|
end
end.to raise_error(File::NotFoundError, "File not found")
end
end
end

View file

@ -1,18 +0,0 @@
require "../spec_helper"
Spectator.describe "GitHub Issue #47" do
class Original
def foo(arg1, arg2)
# ...
end
end
mock Original
let(fake) { mock(Original) }
specify do
expect(fake).to receive(:foo).with("arg1", arg2: "arg2")
fake.foo("arg1", "arg2")
end
end

View file

@ -1,135 +0,0 @@
require "../spec_helper"
Spectator.describe "GitHub Issue #48" do
class Test
def return_this(thing : T) : T forall T
thing
end
def map(thing : T, & : T -> U) : U forall T, U
yield thing
end
def make_nilable(thing : T) : T? forall T
thing.as(T?)
end
def itself : self
self
end
def itself? : self?
self.as(self?)
end
def generic(thing : T) : Array(T) forall T
Array.new(100) { thing }
end
def union : Int32 | String
42.as(Int32 | String)
end
def capture(&block : -> T) forall T
block
end
def capture(thing : T, &block : T -> T) forall T
block.call(thing)
block
end
def range(r : Range)
r
end
end
mock Test, make_nilable: nil
let(fake) { mock(Test) }
it "handles free variables" do
allow(fake).to receive(:return_this).and_return("different")
expect(fake.return_this("test")).to eq("different")
end
it "raises on type cast error with free variables" do
allow(fake).to receive(:return_this).and_return(42)
expect { fake.return_this("test") }.to raise_error(TypeCastError, /String/)
end
it "handles free variables with a block" do
allow(fake).to receive(:map).and_return("stub")
expect(fake.map(:mapped, &.to_s)).to eq("stub")
end
it "raises on type cast error with a block and free variables" do
allow(fake).to receive(:map).and_return(42)
expect { fake.map(:mapped, &.to_s) }.to raise_error(TypeCastError, /String/)
end
it "handles nilable free variables" do
expect(fake.make_nilable("foo")).to be_nil
end
it "handles 'self' return type" do
not_self = mock(Test)
allow(fake).to receive(:itself).and_return(not_self)
expect(fake.itself).to be(not_self)
end
it "raises on type cast error with 'self' return type" do
allow(fake).to receive(:itself).and_return(42)
expect { fake.itself }.to raise_error(TypeCastError, /#{class_mock(Test)}/)
end
it "handles nilable 'self' return type" do
not_self = mock(Test)
allow(fake).to receive(:itself?).and_return(not_self)
expect(fake.itself?).to be(not_self)
end
it "handles generic return type" do
allow(fake).to receive(:generic).and_return([42])
expect(fake.generic(42)).to eq([42])
end
it "raises on type cast error with generic return type" do
allow(fake).to receive(:generic).and_return("test")
expect { fake.generic(42) }.to raise_error(TypeCastError, /Array\(Int32\)/)
end
it "handles union return types" do
allow(fake).to receive(:union).and_return("test")
expect(fake.union).to eq("test")
end
it "raises on type cast error with union return type" do
allow(fake).to receive(:union).and_return(:test)
expect { fake.union }.to raise_error(TypeCastError, /Symbol/)
end
it "handles captured blocks" do
proc = ->{}
allow(fake).to receive(:capture).and_return(proc)
expect(fake.capture { nil }).to be(proc)
end
it "raises on type cast error with captured blocks" do
proc = ->{ 42 }
allow(fake).to receive(:capture).and_return(proc)
expect { fake.capture { "other" } }.to raise_error(TypeCastError, /Proc\(String\)/)
end
it "handles captured blocks with arguments" do
proc = ->(x : Int32) { x * 2 }
allow(fake).to receive(:capture).and_return(proc)
expect(fake.capture(5) { 5 }).to be(proc)
end
it "handles range comparisons against non-comparable types" do
range = 1..10
allow(fake).to receive(:range).and_return(range)
expect(fake.range(1..3)).to eq(range)
end
end

View file

@ -1,6 +0,0 @@
require "../spec_helper"
# https://github.com/icy-arctic-fox/spectator/issues/49
Spectator.describe "GitHub Issue #49" do
# mock File
end

View file

@ -1,48 +0,0 @@
require "../spec_helper"
Spectator.describe "GitHub Issue #55" do
GROUP_NAME = "CallCenter"
let(name) { "TimeTravel" }
let(source) { "my.time.travel.experiment" }
class Analytics(T)
property start_time = Time.local
property end_time = Time.local
def initialize(@brain_talker : T)
end
def instrument(*, name, source, &)
@brain_talker.send(payload: {
:group => GROUP_NAME,
:name => name,
:source => source,
:start => start_time,
:end => end_time,
}, action: "analytics")
end
end
double(:brain_talker, send: nil)
let(brain_talker) { double(:brain_talker) }
let(analytics) { Analytics.new(brain_talker) }
it "tracks the time it takes to run the block" do
analytics.start_time = expected_start_time = Time.local
expected_end_time = expected_start_time + 10.seconds
analytics.end_time = expected_end_time + 0.5.seconds # Offset to ensure non-exact match.
analytics.instrument(name: name, source: source) do
end
expect(brain_talker).to have_received(:send).with(payload: {
:group => GROUP_NAME,
:name => name,
:source => source,
:start => expected_start_time,
:end => be_within(1.second).of(expected_end_time),
}, action: "analytics")
end
end

View file

@ -1,109 +0,0 @@
require "../spec_helper"
module GitLabIssue51
class Foo
def call(str : String) : String?
""
end
def alt1_call(str : String) : String?
nil
end
def alt2_call(str : String) : String?
[str, nil].sample
end
end
class Bar
def call(a_foo) : Nil # Must add nil restriction here, otherwise a segfault occurs from returning the result of #alt2_call.
a_foo.call("")
a_foo.alt1_call("")
a_foo.alt2_call("")
end
end
end
Spectator.describe GitLabIssue51::Bar do
mock GitLabIssue51::Foo, call: "", alt1_call: "", alt2_call: ""
let(:foo) { mock(GitLabIssue51::Foo) }
subject(:call) { described_class.new.call(foo) }
describe "#call" do
it "invokes Foo#call" do
call
expect(foo).to have_received(:call)
end
it "invokes Foo#alt1_call" do
call
expect(foo).to have_received(:alt1_call)
end
it "invokes Foo#alt2_call" do
call
expect(foo).to have_received(:alt2_call)
end
describe "with an explicit return of nil" do
it "should invoke Foo#call?" do
allow(foo).to receive(:call).and_return(nil)
call
expect(foo).to have_received(:call)
end
it "invokes Foo#alt1_call" do
allow(foo).to receive(:alt1_call).and_return(nil)
call
expect(foo).to have_received(:alt1_call)
end
it "invokes Foo#alt2_call" do
allow(foo).to receive(:alt2_call).and_return(nil)
call
expect(foo).to have_received(:alt2_call)
end
end
describe "with returns set in before_each for all calls" do
before_each do
allow(foo).to receive(:call).and_return(nil)
allow(foo).to receive(:alt1_call).and_return(nil)
allow(foo).to receive(:alt2_call).and_return(nil)
end
it "should invoke Foo#call?" do
call
expect(foo).to have_received(:call)
end
it "should invoke Foo#alt1_call?" do
call
expect(foo).to have_received(:alt1_call)
end
it "should invoke Foo#alt2_call?" do
call
expect(foo).to have_received(:alt2_call)
end
end
describe "with returns set in before_each for alt calls only" do
before_each do
allow(foo).to receive(:alt1_call).and_return(nil)
allow(foo).to receive(:alt2_call).and_return(nil)
end
it "invokes Foo#alt1_call" do
call
expect(foo).to have_received(:alt1_call)
end
it "invokes Foo#alt2_call" do
call
expect(foo).to have_received(:alt2_call)
end
end
end
end

View file

@ -1,6 +0,0 @@
require "../spec_helper"
Spectator.describe "GitLab Issue #76" do
let(:value) { nil.as(Int32?) }
specify { expect(value).to be_nil }
end

View file

@ -1,10 +0,0 @@
require "../spec_helper"
# https://gitlab.com/arctic-fox/spectator/-/issues/77
Spectator.describe "GitLab Issue #77" do
it "fails" do
expect_raises do
raise "Error!"
end
end
end

View file

@ -1,30 +0,0 @@
require "../spec_helper"
# https://gitlab.com/arctic-fox/spectator/-/issues/80
class Item
end
class ItemUser
@item = Item.new
def item
@item
end
end
Spectator.describe "test1" do
it "without mock" do
item_user = ItemUser.new
item = item_user.item
item == item
end
end
Spectator.describe "test2" do
mock Item do
end
it "without mock" do
end
end

View file

@ -1,34 +0,0 @@
require "../spec_helper"
Spectator.describe "eq matcher" do
it "is true for equal values" do
expect(42).to eq(42)
end
it "is false for unequal values" do
expect(42).to_not eq(24)
end
it "is true for identical references" do
string = "foobar"
expect(string).to eq(string)
end
it "is false for different references" do
string1 = "foo"
string2 = "bar"
expect(string1).to_not eq(string2)
end
double(:fake) do
stub def ==(other)
true
end
end
it "uses the == operator" do
dbl = double(:fake)
expect(42).to eq(dbl)
expect(dbl).to have_received(:==).with(42)
end
end

View file

@ -1,515 +0,0 @@
require "../spec_helper"
Spectator.describe Spectator::Matchers::ReceiveMatcher do
let(stub) { Spectator::NullStub.new(:test_method) }
subject(matcher) { described_class.new(stub) }
let(args) { Spectator::Arguments.capture(1, "test", Symbol, foo: /bar/) }
let(args_stub) { Spectator::NullStub.new(:test_method, args) }
let(args_matcher) { described_class.new(args_stub) }
let(no_args_stub) { Spectator::NullStub.new(:test_method, Spectator::Arguments.none) }
let(no_args_matcher) { described_class.new(no_args_stub) }
double(:dbl, test_method: nil, irrelevant: nil)
let(dbl) { double(:dbl) }
let(actual) { Spectator::Value.new(dbl, "dbl") }
def successful_match
Spectator::Matchers::SuccessfulMatchData
end
def failed_match
Spectator::Matchers::FailedMatchData
end
describe "#description" do
subject { matcher.description }
it "includes the method name" do
is_expected.to contain("test_method")
end
context "without an argument constraint" do
it "mentions it accepts any arguments" do
is_expected.to contain("any args")
end
end
context "with no arguments" do
let(matcher) { no_args_matcher }
it "mentions there are none" do
is_expected.to contain("no args")
end
end
context "with arguments" do
let(matcher) { args_matcher }
it "lists the arguments" do
is_expected.to contain("1, \"test\", Symbol, foo: #{/bar/.inspect}")
end
end
end
describe "#with" do
subject { matcher.with(1, 2, 3, bar: /baz/) }
it "applies a constraint on arguments" do
dbl.test_method
expect(&.match(actual)).to be_a(failed_match)
dbl.test_method(1, 2, 3, bar: "foobarbaz")
expect(&.match(actual)).to be_a(successful_match)
end
end
describe "#match" do
subject(match_data) { matcher.match(actual) }
post_condition { expect(match_data.description).to contain("dbl received #test_method") }
let(failure_message) { match_data.as(Spectator::Matchers::FailedMatchData).failure_message }
context "with no argument constraint" do
post_condition { expect(&.description).to contain("(any args)") }
it "matches with no arguments" do
dbl.test_method
is_expected.to be_a(successful_match)
end
it "matches with any arguments" do
dbl.test_method("foo")
is_expected.to be_a(successful_match)
end
it "doesn't match with no calls" do
is_expected.to be_a(failed_match)
expect(failure_message).to eq("dbl did not receive #test_method(any args)")
end
it "doesn't match with different calls" do
dbl.irrelevant("foo")
is_expected.to be_a(failed_match)
expect(failure_message).to eq("dbl did not receive #test_method(any args)")
end
end
context "with a \"no arguments\" constraint" do
let(matcher) { no_args_matcher }
post_condition { expect(&.description).to contain("(no args)") }
it "matches with no arguments" do
dbl.test_method
is_expected.to be_a(successful_match)
end
it "doesn't match with arguments" do
dbl.test_method("foo")
is_expected.to be_a(failed_match)
expect(failure_message).to eq("dbl did not receive #test_method(no args)")
end
it "doesn't match with no calls" do
is_expected.to be_a(failed_match)
expect(failure_message).to eq("dbl did not receive #test_method(no args)")
end
it "doesn't match with different calls" do
dbl.irrelevant("foo")
is_expected.to be_a(failed_match)
expect(failure_message).to eq("dbl did not receive #test_method(no args)")
end
end
context "with an arguments constraint" do
let(matcher) { args_matcher }
post_condition { expect(&.description).to contain("(1, \"test\", Symbol, foo: #{/bar/.inspect})") }
it "doesn't match with no arguments" do
dbl.test_method
is_expected.to be_a(failed_match)
expect(failure_message).to eq("dbl did not receive #test_method(1, \"test\", Symbol, foo: #{/bar/.inspect})")
end
it "matches with matching arguments" do
dbl.test_method(1, "test", :xyz, foo: "foobarbaz")
is_expected.to be_a(successful_match)
end
it "doesn't match with differing arguments" do
dbl.test_method(1, "wrong", 42, foo: "wrong")
is_expected.to be_a(failed_match)
expect(failure_message).to eq("dbl did not receive #test_method(1, \"test\", Symbol, foo: #{/bar/.inspect})")
end
it "doesn't match with no calls" do
is_expected.to be_a(failed_match)
expect(failure_message).to eq("dbl did not receive #test_method(1, \"test\", Symbol, foo: #{/bar/.inspect})")
end
it "doesn't match with different calls" do
dbl.irrelevant("foo")
is_expected.to be_a(failed_match)
expect(failure_message).to eq("dbl did not receive #test_method(1, \"test\", Symbol, foo: #{/bar/.inspect})")
end
end
describe "the match data values" do
let(matcher) { args_matcher }
subject(values) { match_data.as(Spectator::Matchers::FailedMatchData).values }
pre_condition { expect(match_data).to be_a(failed_match) }
it "has the expected call listed" do
is_expected.to contain({:expected, "#test_method(1, \"test\", Symbol, foo: #{/bar/.inspect})"})
end
context "with method calls" do
before do
dbl.test_method
dbl.test_method(1, "wrong", :xyz, foo: "foobarbaz")
dbl.irrelevant("foo")
end
it "has the list of called methods" do
is_expected.to contain({
:actual,
<<-SIGNATURES
#test_method(no args)
#test_method(1, "wrong", :xyz, foo: "foobarbaz")
#irrelevant("foo")
SIGNATURES
})
end
end
context "with no method calls" do
it "reports \"None\" for the actual value" do
is_expected.to contain({:actual, "None"})
end
end
end
end
describe "#negated_match" do
subject(match_data) { matcher.negated_match(actual) }
post_condition { expect(match_data.description).to contain("dbl did not receive #test_method") }
let(failure_message) { match_data.as(Spectator::Matchers::FailedMatchData).failure_message }
context "with no argument constraint" do
post_condition { expect(&.description).to contain("(any args)") }
it "doesn't match with no arguments" do
dbl.test_method
is_expected.to be_a(failed_match)
expect(failure_message).to eq("dbl received #test_method(any args)")
end
it "doesn't match with any arguments" do
dbl.test_method("foo")
is_expected.to be_a(failed_match)
expect(failure_message).to eq("dbl received #test_method(any args)")
end
it "matches with no calls" do
is_expected.to be_a(successful_match)
end
it "matches with different calls" do
dbl.irrelevant("foo")
is_expected.to be_a(successful_match)
end
end
context "with a \"no arguments\" constraint" do
let(matcher) { no_args_matcher }
post_condition { expect(&.description).to contain("(no args)") }
it "doesn't match with no arguments" do
dbl.test_method
is_expected.to be_a(failed_match)
expect(failure_message).to eq("dbl received #test_method(no args)")
end
it "matches with arguments" do
dbl.test_method("foo")
is_expected.to be_a(successful_match)
end
it "matches with no calls" do
is_expected.to be_a(successful_match)
end
it "matches with different calls" do
dbl.irrelevant("foo")
is_expected.to be_a(successful_match)
end
end
context "with an arguments constraint" do
let(matcher) { args_matcher }
post_condition { expect(&.description).to contain("(1, \"test\", Symbol, foo: #{/bar/.inspect})") }
it "matches with no arguments" do
dbl.test_method
is_expected.to be_a(successful_match)
end
it "doesn't match with matching arguments" do
dbl.test_method(1, "test", :xyz, foo: "foobarbaz")
is_expected.to be_a(failed_match)
expect(failure_message).to eq("dbl received #test_method(1, \"test\", Symbol, foo: #{/bar/.inspect})")
end
it "matches with differing arguments" do
dbl.test_method(1, "wrong", 42, foo: "wrong")
is_expected.to be_a(successful_match)
end
it "matches with no calls" do
is_expected.to be_a(successful_match)
end
it "matches with different calls" do
dbl.irrelevant("foo")
is_expected.to be_a(successful_match)
end
end
describe "the match data values" do
subject(values) { match_data.as(Spectator::Matchers::FailedMatchData).values }
pre_condition { expect(match_data).to be_a(failed_match) }
before do
dbl.test_method
dbl.test_method(1, "test", :xyz, foo: "foobarbaz")
dbl.irrelevant("foo")
end
it "has the expected call listed" do
is_expected.to contain({:expected, "Not #{stub.message}"})
end
it "has the list of called methods" do
is_expected.to contain({
:actual,
<<-SIGNATURES
#test_method(no args)
#test_method(1, "test", :xyz, foo: "foobarbaz")
#irrelevant("foo")
SIGNATURES
})
end
end
end
describe "#once" do
let(matcher) { super.once }
subject(match_data) { matcher.match(actual) }
it "matches when the stub is called once" do
dbl.test_method
is_expected.to be_a(successful_match)
end
it "doesn't match when the stub isn't called" do
is_expected.to be_a(failed_match)
end
it "doesn't match when the stub is called twice" do
2.times { dbl.test_method }
is_expected.to be_a(failed_match)
end
end
describe "#twice" do
let(matcher) { super.twice }
subject(match_data) { matcher.match(actual) }
it "matches when the stub is called twice" do
2.times { dbl.test_method }
is_expected.to be_a(successful_match)
end
it "doesn't match when the stub isn't called" do
is_expected.to be_a(failed_match)
end
it "doesn't match when the stub is called once" do
dbl.test_method
is_expected.to be_a(failed_match)
end
it "doesn't match when the stub is called thrice" do
3.times { dbl.test_method }
is_expected.to be_a(failed_match)
end
end
describe "#exactly" do
let(matcher) { super.exactly(3) }
subject(match_data) { matcher.match(actual) }
it "matches when the stub is called the exact amount" do
3.times { dbl.test_method }
is_expected.to be_a(successful_match)
end
it "doesn't match when the stub isn't called" do
is_expected.to be_a(failed_match)
end
it "doesn't match when the stub is called less than the amount" do
2.times { dbl.test_method }
is_expected.to be_a(failed_match)
end
it "doesn't match when the stub is called more than the amount" do
4.times { dbl.test_method }
is_expected.to be_a(failed_match)
end
end
describe "#at_least" do
let(matcher) { super.at_least(3) }
subject(match_data) { matcher.match(actual) }
it "matches when the stub is called the exact amount" do
3.times { dbl.test_method }
is_expected.to be_a(successful_match)
end
it "doesn't match when the stub isn't called" do
is_expected.to be_a(failed_match)
end
it "doesn't match when the stub is called less than the amount" do
2.times { dbl.test_method }
is_expected.to be_a(failed_match)
end
it "matches when the stub is called more than the amount" do
4.times { dbl.test_method }
is_expected.to be_a(successful_match)
end
end
describe "#at_most" do
let(matcher) { super.at_most(3) }
subject(match_data) { matcher.match(actual) }
it "matches when the stub is called the exact amount" do
3.times { dbl.test_method }
is_expected.to be_a(successful_match)
end
it "matches when the stub isn't called" do
is_expected.to be_a(successful_match)
end
it "matches when the stub is called less than the amount" do
2.times { dbl.test_method }
is_expected.to be_a(successful_match)
end
it "doesn't match when the stub is called more than the amount" do
4.times { dbl.test_method }
is_expected.to be_a(failed_match)
end
end
describe "#at_least_once" do
let(matcher) { super.at_least_once }
subject(match_data) { matcher.match(actual) }
it "matches when the stub is called once" do
dbl.test_method
is_expected.to be_a(successful_match)
end
it "doesn't match when the stub isn't called" do
is_expected.to be_a(failed_match)
end
it "matches when the stub is called more than once" do
2.times { dbl.test_method }
is_expected.to be_a(successful_match)
end
end
describe "#at_least_twice" do
let(matcher) { super.at_least_twice }
subject(match_data) { matcher.match(actual) }
it "doesn't match when the stub is called once" do
dbl.test_method
is_expected.to be_a(failed_match)
end
it "doesn't match when the stub isn't called" do
is_expected.to be_a(failed_match)
end
it "matches when the stub is called twice" do
2.times { dbl.test_method }
is_expected.to be_a(successful_match)
end
it "matches when the stub is called more than twice" do
3.times { dbl.test_method }
is_expected.to be_a(successful_match)
end
end
describe "#at_most_once" do
let(matcher) { super.at_most_once }
subject(match_data) { matcher.match(actual) }
it "matches when the stub is called once" do
dbl.test_method
is_expected.to be_a(successful_match)
end
it "matches when the stub isn't called" do
is_expected.to be_a(successful_match)
end
it "doesn't match when the stub is called more than once" do
2.times { dbl.test_method }
is_expected.to be_a(failed_match)
end
end
describe "#at_most_twice" do
let(matcher) { super.at_most_twice }
subject(match_data) { matcher.match(actual) }
it "matches when the stub is called once" do
dbl.test_method
is_expected.to be_a(successful_match)
end
it "matches when the stub isn't called" do
is_expected.to be_a(successful_match)
end
it "matches when the stub is called twice" do
2.times { dbl.test_method }
is_expected.to be_a(successful_match)
end
it "doesn't match when the stub is called more than twice" do
3.times { dbl.test_method }
is_expected.to be_a(failed_match)
end
end
end

View file

@ -1,26 +0,0 @@
require "../spec_helper"
Spectator.describe Spectator::Matchers::TypeMatcher do
context String do # Sets `described_class` to String
def other_type
Int32
end
describe "#|" do
it "works on sets" do
super_set = (described_class | other_type)
expect(42).to be_kind_of(super_set)
expect("foo").to be_a(super_set)
end
end
it "works on described_class" do
expect("foo").to be_a_kind_of(described_class)
end
it "works on plain types" do
expect(42).to be_a(Int32)
end
end
end

View file

@ -1,112 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-core/v/3-8/docs/hooks/before-and-after-hooks
# and modified to fit Spectator and Crystal.
Spectator.describe "`before` and `after` hooks" do
context "Define `before_each` block" do
class Thing
def widgets
@widgets ||= [] of Symbol # Must specify array element type.
end
end
describe Thing do
before_each do
@thing = Thing.new
end
describe "initialize in before_each" do
it "has 0 widgets" do
widgets = @thing.as(Thing).widgets # Must cast since compile type is Thing?
expect(widgets.size).to eq(0) # Use size instead of count.
end
it "can accept new widgets" do
widgets = @thing.as(Thing).widgets # Must cast since compile type is Thing?
widgets << :foo
end
it "does not share state across examples" do
widgets = @thing.as(Thing).widgets # Must cast since compile type is Thing?
expect(widgets.size).to eq(0) # Use size instead of count.
end
end
end
end
context "Define `before_all` block in example group" do
class Thing
def widgets
@widgets ||= [] of Symbol # Must specify array element type.
end
end
describe Thing do
# Moved before_all into the same example group.
# Unlike Ruby, inherited class variables don't share the same value.
# See: https://crystal-lang.org/reference/syntax_and_semantics/class_variables.html
describe "initialized in before_all" do
@@thing : Thing?
before_all do
@@thing = Thing.new # Must use class variables.
end
it "has 0 widgets" do
widgets = @@thing.as(Thing).widgets # Must cast since compile type is Thing?
expect(widgets.size).to eq(0) # Use size instead of count.
end
it "can accept new widgets" do
widgets = @@thing.as(Thing).widgets # Must cast since compile type is Thing?
widgets << :foo
end
it "shares state across examples" do
widgets = @@thing.as(Thing).widgets # Must cast since compile type is Thing?
expect(widgets.size).to eq(1) # Use size instead of count.
end
end
end
end
context "Failure in `before_each` block" do
# TODO
end
context "Failure in `after_each` block" do
# TODO
end
context "Define `before` and `after` blocks in configuration" do
# TODO
end
context "`before`/`after` blocks are run in order" do
# Examples changed from using puts to appending to an array.
describe "before and after callbacks" do
@@order = [] of Symbol
before_all do
@@order << :before_all
end
before_each do
@@order << :before_each
end
after_each do
@@order << :after_each
end
after_all do
@@order << :after_all
end
it "gets run in order" do
expect(@@order).to_eventually eq(%i[before_all before_each after_each after_all])
end
end
end
end

View file

@ -1,138 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-core/v/3-8/docs/subject/explicit-subject
# and modified to fit Spectator and Crystal.
Spectator.describe "Explicit Subject" do
context "A `subject` can be defined and used in the top level group scope" do
describe Array(Int32) do # TODO: Multiple arguments to describe/context.
subject { [1, 2, 3] }
it "has the prescribed elements" do
expect(subject).to eq([1, 2, 3])
end
end
end
context "The `subject` define in an outer group is available to inner groups" do
describe Array(Int32) do
subject { [1, 2, 3] }
describe "has some elements" do
it "which are the prescribed elements" do
expect(subject).to eq([1, 2, 3])
end
end
end
end
context "The `subject` is memoized within an example but not across examples" do
describe Array(Int32) do
# Changed to class variable to get around compiler error/crash.
# Unhandled exception: Negative argument (ArgumentError)
@@element_list = [1, 2, 3]
subject { @@element_list.pop }
it "is memoized across calls (i.e. the block is invoked once)" do
expect do
3.times { subject }
end.to change { @@element_list }.from([1, 2, 3]).to([1, 2])
expect(subject).to eq(3)
end
it "is not memoized across examples" do
expect { subject }.to change { @@element_list }.from([1, 2]).to([1])
expect(subject).to eq(2)
end
end
end
context "The `subject` is available in `before` blocks" do
describe Array(Int32) do # TODO: Multiple arguments to describe/context.
subject { [] of Int32 }
before { subject.push(1, 2, 3) }
it "has the prescribed elements" do
expect(subject).to eq([1, 2, 3])
end
end
end
context "Helper methods can be invoked from a `subject` definition block" do
describe Array(Int32) do # TODO: Multiple arguments to describe/context.
def prepared_array
[1, 2, 3]
end
subject { prepared_array }
it "has the prescribed elements" do
expect(subject).to eq([1, 2, 3])
end
end
end
context "Use the `subject!` bang method to call the definition block before the example" do
describe "eager loading with subject!" do
subject! { element_list.push(99) }
let(:element_list) { [1, 2, 3] }
it "calls the definition block before the example" do
element_list.push(5)
expect(element_list).to eq([1, 2, 3, 99, 5])
end
end
end
context "Use `subject(:name)` to define a memoized helper method" do
# Globals not supported, using class variable instead.
@@count = 0
describe "named subject" do
subject(:global_count) { @@count += 1 }
it "is memoized across calls (i.e. the block is invoked once)" do
expect do
2.times { global_count }
end.not_to change { global_count }.from(1)
end
it "is not cached across examples" do
expect(global_count).to eq(2)
end
it "is still available using the subject method" do
expect(subject).to eq(3)
end
it "works with the one-liner syntax" do
is_expected.to eq(4)
end
it "the subject and named helpers return the same object" do
expect(global_count).to be(subject)
end
it "is set to the block return value (i.e. the global $count)" do
expect(global_count).to be(@@count)
end
end
end
context "Use `subject!(:name)` to define a helper method called before the example" do
describe "eager loading using a named subject!" do
subject!(:updated_list) { element_list.push(99) }
let(:element_list) { [1, 2, 3] }
it "calls the definition block before the example" do
element_list.push(5)
expect(element_list).to eq([1, 2, 3, 99, 5])
expect(updated_list).to be(element_list)
end
end
end
end

View file

@ -1,29 +0,0 @@
require "../../spec_helper"
Spectator.describe "Arbitrary helper methods" do
context "Use a method define in the same group" do
describe "an example" do
def help
:available
end
it "has access to methods define in its group" do
expect(help).to be(:available)
end
end
end
context "Use a method defined in a parent group" do
describe "an example" do
def help
:available
end
describe "in a nested group" do
it "has access to methods defined in its parent group" do
expect(help).to be(:available)
end
end
end
end
end

View file

@ -1,43 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-core/v/3-8/docs/subject/implicitly-defined-subject
# and modified to fit Spectator and Crystal.
Spectator.describe "Implicitly defined subject" do
context "`subject` exposed in top-level group" do
describe Array(String) do
it "should be empty when first created" do
expect(subject).to be_empty
end
end
end
context "`subject` in a nested group" do
describe Array(String) do
describe "when first created" do
it "should be empty" do
expect(subject).to be_empty
end
end
end
end
context "`subject` in a nested group with a different class (innermost wins)" do
class ArrayWithOneElement < Array(String)
def initialize(*_args)
super
unshift "first element"
end
end
describe Array(String) do
describe ArrayWithOneElement do
context "referenced as subject" do
it "contains one element" do
expect(subject).to contain("first element")
end
end
end
end
end
end

View file

@ -1,45 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-core/v/3-8/docs/helper-methods/let-and-let
# and modified to fit Spectator and Crystal.
Spectator.describe "Let and let!" do
context "Use `let` to define memoized helper method" do
# Globals aren't supported, use class variables instead.
@@count = 0
describe "let" do
let(:count) { @@count += 1 }
it "memoizes the value" do
expect(count).to eq(1)
expect(count).to eq(1)
end
it "is not cached across examples" do
expect(count).to eq(2)
end
end
end
context "Use `let!` to define a memoized helper method that is called in a `before` hook" do
# Globals aren't supported, use class variables instead.
@@count = 0
describe "let!" do
# Use class variable here.
@@invocation_order = [] of Symbol
let!(:count) do
@@invocation_order << :let!
@@count += 1
end
it "calls the helper method in a before hook" do
@@invocation_order << :example
expect(@@invocation_order).to eq([:let!, :example])
expect(count).to eq(1)
end
end
end
end

View file

@ -1,31 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-core/v/3-8/docs/subject/one-liner-syntax
# and modified to fit Spectator and Crystal.
Spectator.describe "One-liner syntax" do
context "Implicit subject" do
describe Array(Int32) do
# Rather than:
# it "should be empty" do
# subject.should be_empty
# end
it { should be_empty }
# or
it { is_expected.to be_empty }
end
end
context "Explicit subject" do
describe Array(Int32) do
describe "with 3 items" do
subject { [1, 2, 3] }
it { should_not be_empty }
# or
it { is_expected.not_to be_empty }
end
end
end
end

View file

@ -1,35 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/all-matcher
# and modified to fit Spectator and Crystal.
Spectator.describe "`all` matcher" do
context "array usage" do
describe [1, 3, 5] do
it { is_expected.to all(be_odd) }
it { is_expected.to all(be_an(Int32)) } # Changed to Int32 to satisfy compiler.
it { is_expected.to all(be < 10) }
# deliberate failures
it_fails { is_expected.to all(be_even) }
it_fails { is_expected.to all(be_a(String)) }
it_fails { is_expected.to all(be > 2) }
end
end
context "compound matcher usage" do
# Changed `include` to `contain` to match our own.
# `include` is a keyword and can't be used as a method name in Crystal.
describe ["anything", "everything", "something"] do
skip reason: "Add support for compound matchers." { is_expected.to all(be_a(String).and contain("thing")) }
skip reason: "Add support for compound matchers." { is_expected.to all(be_a(String).and end_with("g")) }
skip reason: "Add support for compound matchers." { is_expected.to all(start_with("s").or contain("y")) }
# deliberate failures
skip reason: "Add support for compound matchers." { is_expected.to all(contain("foo").and contain("bar")) }
skip reason: "Add support for compound matchers." { is_expected.to all(be_a(String).and start_with("a")) }
skip reason: "Add support for compound matchers." { is_expected.to all(start_with("a").or contain("z")) }
end
end
end

View file

@ -1,17 +0,0 @@
require "../../spec_helper"
Spectator.describe "`be_between` matcher" do
context "basic usage" do
describe 7 do
it { is_expected.to be_between(1, 10) }
it { is_expected.to be_between(0.2, 27.1) }
it { is_expected.not_to be_between(1.5, 4) }
it { is_expected.not_to be_between(8, 9) }
# boundaries check
it { is_expected.to be_between(0, 7) }
it { is_expected.to be_between(7, 10) }
it { is_expected.not_to(be_between(0, 7).exclusive) }
end
end
end

View file

@ -1,66 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/be-matchers
# and modified to fit Spectator and Crystal.
Spectator.describe "`be` matchers" do
context "be_truthy matcher" do
specify { expect(true).to be_truthy }
specify { expect(7).to be_truthy }
specify { expect("foo").to be_truthy }
specify { expect(nil).not_to be_truthy }
specify { expect(false).not_to be_truthy }
# deliberate failures
specify_fails { expect(true).not_to be_truthy }
specify_fails { expect(7).not_to be_truthy }
specify_fails { expect("foo").not_to be_truthy }
specify_fails { expect(nil).to be_truthy }
specify_fails { expect(false).to be_truthy }
end
context "be_falsey matcher" do
specify { expect(nil).to be_falsey }
specify { expect(false).to be_falsey }
specify { expect(true).not_to be_falsey }
specify { expect(7).not_to be_falsey }
specify { expect("foo").not_to be_falsey }
# deliberate failures
specify_fails { expect(nil).not_to be_falsey }
specify_fails { expect(false).not_to be_falsey }
specify_fails { expect(true).to be_falsey }
specify_fails { expect(7).to be_falsey }
specify_fails { expect("foo").to be_falsey }
end
context "be_nil matcher" do
specify { expect(nil).to be_nil }
specify { expect(false).not_to be_nil }
specify { expect(true).not_to be_nil }
specify { expect(7).not_to be_nil }
specify { expect("foo").not_to be_nil }
# deliberate failures
specify_fails { expect(nil).not_to be_nil }
specify_fails { expect(false).to be_nil }
specify_fails { expect(true).to be_nil }
specify_fails { expect(7).to be_nil }
specify_fails { expect("foo").to be_nil }
end
context "be matcher" do
specify { expect(true).to be }
specify { expect(7).to be }
specify { expect("foo").to be }
specify { expect(nil).not_to be }
specify { expect(false).not_to be }
# deliberate failures
specify_fails { expect(true).not_to be }
specify_fails { expect(7).not_to be }
specify_fails { expect("foo").not_to be }
specify_fails { expect(nil).to be }
specify_fails { expect(false).to be }
end
end

View file

@ -1,24 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/be-within-matcher
# and modified to fit Spectator and Crystal.
Spectator.describe "`be_within` matcher" do
context "basic usage" do
describe 27.5 do
it { is_expected.to be_within(0.5).of(27.9) }
it { is_expected.to be_within(0.5).of(28.0) }
it { is_expected.to be_within(0.5).of(27.1) }
it { is_expected.to be_within(0.5).of(27.0) }
it { is_expected.not_to be_within(0.5).of(28.1) }
it { is_expected.not_to be_within(0.5).of(26.9) }
# deliberate failures
it_fails { is_expected.not_to be_within(0.5).of(28) }
it_fails { is_expected.not_to be_within(0.5).of(27) }
it_fails { is_expected.to be_within(0.5).of(28.1) }
it_fails { is_expected.to be_within(0.5).of(26.9) }
end
end
end

View file

@ -1,47 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/change-matcher
# and modified to fit Spectator and Crystal.
Spectator.describe "`change` matcher" do
# Modified this example type to work in Crystal.
module Counter
extend self
@@count = 0
def increment
@@count += 1
end
def count
@@count
end
end
context "expect change" do
describe "Counter#increment" do # TODO: Allow multiple arguments to context/describe.
it "should increment the count" do
expect { Counter.increment }.to change { Counter.count }.from(0).to(1)
end
# deliberate failure
it_fails "should increment the count by 2" do
expect { Counter.increment }.to change { Counter.count }.by(2)
end
end
end
context "expect no change" do
describe "Counter#increment" do # TODO: Allow multiple arguments to context/describe.
# deliberate failures
it_fails "should not increment the count by 1 (using not_to)" do
expect { Counter.increment }.not_to change { Counter.count }
end
it_fails "should not increment the count by 1 (using to_not)" do
expect { Counter.increment }.to_not change { Counter.count }
end
end
end
end

View file

@ -1,44 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/comparison-matchers
# and modified to fit Spectator and Crystal.
Spectator.describe "Comparison matchers" do
context "numeric operator matchers" do
describe 18 do
it { is_expected.to be < 20 }
it { is_expected.to be > 15 }
it { is_expected.to be <= 19 }
it { is_expected.to be >= 17 }
# deliberate failures
it_fails { is_expected.to be < 15 }
it_fails { is_expected.to be > 20 }
it_fails { is_expected.to be <= 17 }
it_fails { is_expected.to be >= 19 }
# it { is_expected.to be < 'a' } # Removed because Crystal doesn't support Int32#<(Char)
end
describe 'a' do
it { is_expected.to be < 'b' }
# deliberate failures
# it { is_expected.to be < 18 } # Removed because Crystal doesn't support Char#<(Int32)
end
end
context "string operator matchers" do
describe "Strawberry" do
it { is_expected.to be < "Tomato" }
it { is_expected.to be > "Apple" }
it { is_expected.to be <= "Turnip" }
it { is_expected.to be >= "Banana" }
# deliberate failures
it_fails { is_expected.to be < "Cranberry" }
it_fails { is_expected.to be > "Zuchini" }
it_fails { is_expected.to be <= "Potato" }
it_fails { is_expected.to be >= "Tomato" }
end
end
end

View file

@ -1,30 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/contain-exactly-matcher
# and modified to fit Spectator and Crystal.
Spectator.describe "`contain_exactly` matcher" do
context "Array is expected to contain every value" do
describe [1, 2, 3] do
it { is_expected.to contain_exactly(1, 2, 3) }
it { is_expected.to contain_exactly(1, 3, 2) }
it { is_expected.to contain_exactly(2, 1, 3) }
it { is_expected.to contain_exactly(2, 3, 1) }
it { is_expected.to contain_exactly(3, 1, 2) }
it { is_expected.to contain_exactly(3, 2, 1) }
# deliberate failures
it_fails { is_expected.to contain_exactly(1, 2, 1) }
end
end
context "Array is not expected to contain every value" do
describe [1, 2, 3] do
it { is_expected.to_not contain_exactly(1, 2, 3, 4) }
it { is_expected.to_not contain_exactly(1, 2) }
# deliberate failures
it_fails { is_expected.to_not contain_exactly(1, 3, 2) }
end
end
end

View file

@ -1,90 +0,0 @@
require "../../spec_helper"
# In Ruby, this is the `include` matcher.
# However, `include` is a reserved keyword in Crystal.
# So instead, it is `contain` in Spectator.
Spectator.describe "`contain` matcher" do
context "array usage" do
describe [1, 3, 7] do
it { is_expected.to contain(1) }
it { is_expected.to contain(3) }
it { is_expected.to contain(7) }
it { is_expected.to contain(1, 7) }
it { is_expected.to contain(1, 3, 7) }
skip reason: "Utility matcher method `a_kind_of` is not supported." { is_expected.to contain(a_kind_of(Int)) }
skip reason: "Compound matchers aren't supported." { is_expected.to contain(be_odd.and be < 10) }
# TODO: Fix behavior and cleanup output.
# This syntax is allowed, but produces a wrong result and bad output.
skip reason: "Fix behavior and cleanup output." { is_expected.to contain(be_odd) }
skip reason: "Fix behavior and cleanup output." { is_expected.not_to contain(be_even) }
it { is_expected.not_to contain(17) }
it { is_expected.not_to contain(43, 100) }
# deliberate failures
it_fails { is_expected.to contain(4) }
it_fails { is_expected.to contain(be_even) }
it_fails { is_expected.not_to contain(1) }
it_fails { is_expected.not_to contain(3) }
it_fails { is_expected.not_to contain(7) }
it_fails { is_expected.not_to contain(1, 3, 7) }
# both of these should fail since it contains 1 but not 9
it_fails { is_expected.to contain(1, 9) }
it_fails { is_expected.not_to contain(1, 9) }
end
end
context "string usage" do
describe "a string" do
it { is_expected.to contain("str") }
it { is_expected.to contain("a", "str", "ng") }
it { is_expected.not_to contain("foo") }
it { is_expected.not_to contain("foo", "bar") }
# deliberate failures
it_fails { is_expected.to contain("foo") }
it_fails { is_expected.not_to contain("str") }
it_fails { is_expected.to contain("str", "foo") }
it_fails { is_expected.not_to contain("str", "foo") }
end
end
context "hash usage" do
# A hash can't be described inline here for some reason.
# So it is placed in the subject instead.
describe ":a => 7, :b => 5" do
subject { {:a => 7, :b => 5} }
# Hash syntax is changed here from `:a => 7` to `a: 7`.
skip reason: "This hash-like syntax isn't supported." { is_expected.to contain(a: 7) }
skip reason: "This hash-like syntax isn't supported." { is_expected.to contain(b: 5, a: 7) }
skip reason: "This hash-like syntax isn't supported." { is_expected.not_to contain(:c) }
skip reason: "This hash-like syntax isn't supported." { is_expected.not_to contain(:c, :d) }
skip reason: "This hash-like syntax isn't supported." { is_expected.not_to contain(d: 2) }
skip reason: "This hash-like syntax isn't supported." { is_expected.not_to contain(a: 5) }
skip reason: "This hash-like syntax isn't supported." { is_expected.not_to contain(b: 7, a: 5) }
# deliberate failures
skip reason: "This hash-like syntax isn't supported." { is_expected.not_to contain(:a) }
skip reason: "This hash-like syntax isn't supported." { is_expected.not_to contain(:b, :a) }
skip reason: "This hash-like syntax isn't supported." { is_expected.not_to contain(a: 7) }
skip reason: "This hash-like syntax isn't supported." { is_expected.not_to contain(a: 7, b: 5) }
skip reason: "This hash-like syntax isn't supported." { is_expected.to contain(:c) }
skip reason: "This hash-like syntax isn't supported." { is_expected.to contain(:c, :d) }
skip reason: "This hash-like syntax isn't supported." { is_expected.to contain(d: 2) }
skip reason: "This hash-like syntax isn't supported." { is_expected.to contain(a: 5) }
skip reason: "This hash-like syntax isn't supported." { is_expected.to contain(a: 5, b: 7) }
# Mixed cases--the hash contains one but not the other.
# All 4 of these cases should fail.
skip reason: "This hash-like syntax isn't supported." { is_expected.to contain(:a, :d) }
skip reason: "This hash-like syntax isn't supported." { is_expected.not_to contain(:a, :d) }
skip reason: "This hash-like syntax isn't supported." { is_expected.to contain(a: 7, d: 3) }
skip reason: "This hash-like syntax isn't supported." { is_expected.not_to contain(a: 7, d: 3) }
end
end
end

View file

@ -1,29 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/cover-matcher
# and modified to fit Spectator and Crystal.
Spectator.describe "`cover` matcher" do
context "range usage" do
describe (1..10) do
it { is_expected.to cover(4) }
it { is_expected.to cover(6) }
it { is_expected.to cover(8) }
it { is_expected.to cover(4, 6) }
it { is_expected.to cover(4, 6, 8) }
it { is_expected.not_to cover(11) }
it { is_expected.not_to cover(11, 12) }
# deliberate failures
it_fails { is_expected.to cover(11) }
it_fails { is_expected.not_to cover(4) }
it_fails { is_expected.not_to cover(6) }
it_fails { is_expected.not_to cover(8) }
it_fails { is_expected.not_to cover(4, 6, 8) }
# both of these should fail since it covers 5 but not 11
it_fails { is_expected.to cover(5, 11) }
it_fails { is_expected.not_to cover(5, 11) }
end
end
end

View file

@ -1,30 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/end-with-matcher
# and modified to fit Spectator and Crystal.
Spectator.describe "`end_with` matcher" do
context "string usage" do
describe "this string" do
it { is_expected.to end_with "string" }
it { is_expected.not_to end_with "stringy" }
# deliberate failures
it_fails { is_expected.not_to end_with "string" }
it_fails { is_expected.to end_with "stringy" }
end
end
context "array usage" do
describe [0, 1, 2, 3, 4] do
it { is_expected.to end_with 4 }
skip reason: "Add support for multiple items at the end of an array." { is_expected.to end_with 3, 4 }
it { is_expected.not_to end_with 3 }
skip reason: "Add support for multiple items at the end of an array." { is_expected.not_to end_with 0, 1, 2, 3, 4, 5 }
# deliberate failures
it_fails { is_expected.not_to end_with 4 }
it_fails { is_expected.to end_with 3 }
end
end
end

View file

@ -1,64 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/equality-matchers
# and modified to fit Spectator and Crystal.
Spectator.describe "Equality matchers" do
context "compare using eq (==)" do
describe "a string" do
it "is equal to another string of the same value" do
expect("this string").to eq("this string")
end
it "is not equal to another string of a different value" do
expect("this string").not_to eq("a different string")
end
end
describe "an integer" do
it "is equal to a float for the same value" do
expect(5).to eq(5.0)
end
end
end
context "compare using ==" do
describe "a string" do
it "is equal to another string of the same value" do
expect("this string").to be == "this string"
end
it "is not equal to another string of a different value" do
expect("this string").not_to be == "a different string"
end
end
describe "an integer" do
it "is equal to a float of the same value" do
expect(5).to be == 5.0
end
end
end
# There are no #eql? and #equal? methods in Crystal, so these tests are skipped.
context "compare using be (same?)" do
it "is equal to itself" do
string = "this string"
expect(string).to be(string)
end
it "is not equal to another reference of the same value" do
# Strings with identical contents are the same reference in Crystal.
# This test is modified to reflect that.
# expect("this string").not_to be("this string")
box1 = Box.new("this string")
box2 = Box.new("this string")
expect(box1).not_to be(box2)
end
it "is not equal to another string of a different value" do
expect("this string").not_to be("a different string")
end
end
end

View file

@ -1,36 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/have-attributes-matcher
# and modified to fit Spectator and Crystal.
Spectator.describe "`have_attributes` matcher" do
context "basic usage" do
# Use `record` instead of `Struct.new`.
record Person, name : String, age : Int32
describe Person.new("Jim", 32) do
# Changed some syntax for Ruby hashes to Crystal named tuples.
# Spectator doesn't support helper matchers like `a_string_starting_with` and `a_value <`.
# But maybe in the future it will.
it { is_expected.to have_attributes(name: "Jim") }
skip reason: "Add support for fuzzy matchers." { is_expected.to have_attributes(name: a_string_starting_with("J")) }
it { is_expected.to have_attributes(age: 32) }
skip reason: "Add support for fuzzy matchers." { is_expected.to have_attributes(age: (a_value > 30)) }
it { is_expected.to have_attributes(name: "Jim", age: 32) }
skip reason: "Add support for fuzzy matchers." { is_expected.to have_attributes(name: a_string_starting_with("J"), age: (a_value > 30)) }
it { is_expected.not_to have_attributes(name: "Bob") }
it { is_expected.not_to have_attributes(age: 10) }
skip reason: "Add support for fuzzy matchers." { is_expected.not_to have_attributes(age: (a_value < 30)) }
# deliberate failures
it_fails { is_expected.to have_attributes(name: "Bob") }
it_fails { is_expected.to have_attributes(name: 10) }
# fails if any of the attributes don't match
it_fails { is_expected.to have_attributes(name: "Bob", age: 32) }
it_fails { is_expected.to have_attributes(name: "Jim", age: 10) }
it_fails { is_expected.to have_attributes(name: "Bob", age: 10) }
end
end
end

View file

@ -1,28 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/match-matcher
# and modified to fit Spectator and Crystal.
Spectator.describe "`match` matcher" do
context "string usage" do
describe "a string" do
it { is_expected.to match(/str/) }
it { is_expected.not_to match(/foo/) }
# deliberate failures
it_fails { is_expected.not_to match(/str/) }
it_fails { is_expected.to match(/foo/) }
end
end
context "regular expression usage" do
describe /foo/ do
it { is_expected.to match("food") }
it { is_expected.not_to match("drinks") }
# deliberate failures
it_fails { is_expected.not_to match("food") }
it_fails { is_expected.to match("drinks") }
end
end
end

View file

@ -1,81 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/predicate-matchers
# and modified to fit Spectator and Crystal.
Spectator.describe "Predicate matchers" do
context "should be_zero (based on Int#zero?)" do
describe 0 do
it { is_expected.to be_zero }
end
describe 7 do
# deliberate failure
it_fails { is_expected.to be_zero }
end
end
context "should_not be_empty (based on Array#empty?)" do
describe [1, 2, 3] do
it { is_expected.not_to be_empty }
end
describe [] of Int32 do
# deliberate failure
it_fails { is_expected.not_to be_empty }
end
end
context "should have_key (based on Hash#has_key?)" do
describe Hash do
subject { {:foo => 7} }
it { is_expected.to have_key(:foo) }
# deliberate failure
it_fails { is_expected.to have_key(:bar) }
end
end
context "should_not have_all_string_keys (based on custom #has_all_string_keys? method)" do
class ::Hash(K, V)
def has_all_string_keys?
keys.all? { |k| String === k }
end
end
describe Hash do
context "with symbol keys" do
subject { {:foo => 7, :bar => 5} }
it { is_expected.not_to have_all_string_keys }
end
context "with string keys" do
subject { {"foo" => 7, "bar" => 5} }
# deliberate failure
it_fails { is_expected.not_to have_all_string_keys }
end
end
end
context "matcher arguments are passed on to the predicate method" do
struct ::Int
def multiple_of?(x)
(self % x).zero?
end
end
describe 12 do
it { is_expected.to be_multiple_of(3) }
it { is_expected.not_to be_multiple_of(7) }
# deliberate failures
it_fails { is_expected.not_to be_multiple_of(4) }
it_fails { is_expected.to be_multiple_of(5) }
end
end
# The examples using private methods cause a compilation error in Crystal, and can't be used here.
end

View file

@ -1,93 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/raise-error-matcher
# and modified to fit Spectator and Crystal.
Spectator.describe "`raise_error` matcher" do
context "expect any error" do
# This example originally calls a non-existent method.
# That isn't allowed in Crystal.
# The example has been changed to just raise a runtime error.
describe "dividing by zero" do
it "raises" do
expect { 42 // 0 }.to raise_error
end
end
end
context "expect specific error" do
# Again, can't even compile if a method doesn't exist.
# So using a different exception here.
describe "dividing by zero" do
it "raises" do
expect { 42 // 0 }.to raise_error(DivisionByZeroError)
end
end
end
# The following examples are changed slightly.
# `raise Type.new(message)` is the syntax in Crystal,
# whereas it is `raise Type, message` in Ruby.
# Additionally, `StandardError` doesn't exist in Crystal,
# so `Exception` is used instead.
context "match message with a string" do
describe "matching error message with string" do
it "matches the error message" do
expect { raise Exception.new("this message exactly") }
.to raise_error("this message exactly")
end
end
end
context "match message with a regexp" do
describe "matching error message with regex" do
it "matches the error message" do
expect { raise Exception.new("my message") }
.to raise_error(/my mess/)
end
end
end
context "matching message with `with_message`" do
describe "matching error message with regex" do
it "matches the error message" do
expect { raise Exception.new("my message") }
.to raise_error.with_message(/my mess/)
end
end
end
context "match class + message with string" do
describe "matching error message with string" do
it "matches the error message" do
expect { raise Exception.new("this message exactly") }
.to raise_error(Exception, "this message exactly")
end
end
end
context "match class + message with regexp" do
describe "matching error message with regex" do
it "matches the error message" do
expect { raise Exception.new("my message") }
.to raise_error(Exception, /my mess/)
end
end
end
context "set expectations on error object passed to block" do
skip "raises DivisionByZeroError", reason: "Support passing a block to `raise_error` matcher." do
expect { 42 // 0 }.to raise_error do |error|
expect(error).to be_a(DivisionByZeroError)
end
end
end
context "expect no error at all" do
describe "#to_s" do
it "does not raise" do
expect { 42.to_s }.not_to raise_error
end
end
end
end

View file

@ -1,28 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/respond-to-matcher
# and modified to fit Spectator and Crystal.
Spectator.describe "`respond_to` matcher" do
context "basic usage" do
describe "a string" do
it { is_expected.to respond_to(:size) } # It's size in Crystal, not length.
it { is_expected.to respond_to(:hash, :class, :to_s) }
it { is_expected.not_to respond_to(:to_model) }
it { is_expected.not_to respond_to(:compact, :flatten) }
# deliberate failures
it_fails { is_expected.to respond_to(:to_model) }
it_fails { is_expected.to respond_to(:compact, :flatten) }
it_fails { is_expected.not_to respond_to(:size) }
it_fails { is_expected.not_to respond_to(:hash, :class, :to_s) }
# mixed examples--String responds to :length but not :flatten
# both specs should fail
it_fails { is_expected.to respond_to(:size, :flatten) }
it_fails { is_expected.not_to respond_to(:size, :flatten) }
end
end
# Spectator doesn't support argument matching with respond_to.
end

View file

@ -1,30 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/start-with-matcher
# and modified to fit Spectator and Crystal.
Spectator.describe "`start_with` matcher" do
context "with a string" do
describe "this string" do
it { is_expected.to start_with "this" }
it { is_expected.not_to start_with "that" }
# deliberate failures
it_fails { is_expected.not_to start_with "this" }
it_fails { is_expected.to start_with "that" }
end
end
context "with an array" do
describe [0, 1, 2, 3, 4] do
it { is_expected.to start_with 0 }
skip reason: "Add support for multiple items at the beginning of an array." { is_expected.to start_with(0, 1) }
it { is_expected.not_to start_with(2) }
skip reason: "Add support for multiple items at the beginning of an array." { is_expected.not_to start_with(0, 1, 2, 3, 4, 5) }
# deliberate failures
it_fails { is_expected.not_to start_with 0 }
it_fails { is_expected.to start_with 3 }
end
end
end

View file

@ -1,100 +0,0 @@
require "../../spec_helper"
# Examples taken from:
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/type-matchers
# and modified to fit Spectator and Crystal.
Spectator.describe "Type matchers" do
context "be_(a_)kind_of matcher" do
# The docs use Float as an example.
# This doesn't work with the Crystal compiler,
# so a custom hierarchy is used instead.
# "Error: can't use Number as generic type argument yet, use a more specific type"
module MyModule; end
class Base; end
class Derived < Base
include MyModule
end
describe Derived do
# the actual class
it { is_expected.to be_kind_of(Derived) }
it { is_expected.to be_a_kind_of(Derived) }
it { is_expected.to be_a(Derived) }
# the superclass
it { is_expected.to be_kind_of(Base) }
it { is_expected.to be_a_kind_of(Base) }
it { is_expected.to be_an(Base) }
# an included module
it { is_expected.to be_kind_of(MyModule) }
it { is_expected.to be_a_kind_of(MyModule) }
it { is_expected.to be_a(MyModule) }
# negative passing case
it { is_expected.not_to be_kind_of(String) }
it { is_expected.not_to be_a_kind_of(String) }
it { is_expected.not_to be_a(String) }
# deliberate failures
it_fails { is_expected.not_to be_kind_of(Derived) }
it_fails { is_expected.not_to be_a_kind_of(Derived) }
it_fails { is_expected.not_to be_a(Derived) }
it_fails { is_expected.not_to be_kind_of(Base) }
it_fails { is_expected.not_to be_a_kind_of(Base) }
it_fails { is_expected.not_to be_an(Base) }
it_fails { is_expected.not_to be_kind_of(MyModule) }
it_fails { is_expected.not_to be_a_kind_of(MyModule) }
it_fails { is_expected.not_to be_a(MyModule) }
it_fails { is_expected.to be_kind_of(String) }
it_fails { is_expected.to be_a_kind_of(String) }
it_fails { is_expected.to be_a(String) }
end
context "be_(an_)instance_of matcher" do
# The docs use Float as an example.
# This doesn't work with the Crystal compiler,
# so a custom hierarchy is used instead.
# "Error: can't use Number as generic type argument yet, use a more specific type"
module MyModule; end
class Base; end
class Derived < Base
include MyModule
end
describe Derived do
# the actual class
it { is_expected.to be_instance_of(Derived) }
it { is_expected.to be_an_instance_of(Derived) }
# the superclass
it { is_expected.not_to be_instance_of(Base) }
it { is_expected.not_to be_an_instance_of(Base) }
# an included module
it { is_expected.not_to be_instance_of(MyModule) }
it { is_expected.not_to be_an_instance_of(MyModule) }
# another class with no relation to the subject's hierarchy
it { is_expected.not_to be_instance_of(String) }
it { is_expected.not_to be_an_instance_of(String) }
# deliberate failures
it_fails { is_expected.not_to be_instance_of(Derived) }
it_fails { is_expected.not_to be_an_instance_of(Derived) }
it_fails { is_expected.to be_instance_of(Base) }
it_fails { is_expected.to be_an_instance_of(Base) }
it_fails { is_expected.to be_instance_of(MyModule) }
it_fails { is_expected.to be_an_instance_of(MyModule) }
it_fails { is_expected.to be_instance_of(String) }
it_fails { is_expected.to be_an_instance_of(String) }
end
end
end
end

View file

@ -1,17 +1,2 @@
require "json" # Needed to test masking Object#to_json in doubles.
require "yaml" # Needed to test masking Object#to_yaml in doubles.
require "spec"
require "../src/spectator"
require "../src/spectator/should"
require "./helpers/**"
macro it_fails(description = nil, &block)
it {{description}} do
expect do
{{block.body}}
end.to raise_error(Spectator::ExampleFailed)
end
end
macro specify_fails(description = nil, &block)
it_fails {{description}} {{block}}
end

View file

@ -1,24 +0,0 @@
require "../spec_helper"
Spectator.describe Spectator::Anything do
it "matches everything" do
expect(true).to match(subject)
expect(false).to match(subject)
expect(nil).to match(subject)
expect(42).to match(subject)
expect(42.as(Int32 | String)).to match(subject)
expect(["foo", "bar"]).to match(subject)
end
describe "#to_s" do
subject { super.to_s }
it { is_expected.to contain("anything") }
end
describe "#inspect" do
subject { super.inspect }
it { is_expected.to contain("anything") }
end
end

View file

@ -1,34 +0,0 @@
require "../spec_helper"
Spectator.describe Spectator::Block do
describe "#value" do
it "calls the block" do
called = false
block = described_class.new { called = true }
expect { block.value }.to change { called }.to(true)
end
it "can be called multiple times (doesn't cache the value)" do
count = 0
block = described_class.new { count += 1 }
block.value # Call once, count should be 1.
expect { block.value }.to change { count }.from(1).to(2)
end
end
describe "#to_s" do
let(block) do
described_class.new("Test Label") { 42 }
end
subject { block.to_s }
it "contains the label" do
is_expected.to contain("Test Label")
end
it "contains the value" do
is_expected.to contain("42")
end
end
end

View file

@ -1,188 +0,0 @@
require "../../../spec_helper"
Spectator.describe "Allow stub DSL" do
context "with a double" do
double(:dbl) do
# Ensure the original is never called.
stub abstract def foo : Nil
stub abstract def foo(arg) : Nil
stub abstract def value : Int32
end
let(dbl) { double(:dbl) }
# Ensure invocations don't leak between examples.
pre_condition { expect(dbl).to_not have_received(:foo), "Leaked method calls from previous examples" }
# Ensure stubs don't leak between examples.
pre_condition do
expect { dbl.foo }.to raise_error(Spectator::UnexpectedMessage)
end
it "matches when a message is received" do
allow(dbl).to receive(:foo)
expect { dbl.foo }.to_not raise_error
end
it "returns the correct value" do
allow(dbl).to receive(:value).and_return(42)
expect(dbl.value).to eq(42)
end
it "matches when a message is received with matching arguments" do
allow(dbl).to receive(:foo).with(:bar)
expect { dbl.foo(:bar) }.to_not raise_error
end
it "raises when a message without arguments is received" do
allow(dbl).to receive(:foo).with(:bar)
expect { dbl.foo }.to raise_error(Spectator::UnexpectedMessage, /foo/)
end
it "raises when a message with different arguments is received" do
allow(dbl).to receive(:foo).with(:baz)
expect { dbl.foo(:bar) }.to raise_error(Spectator::UnexpectedMessage, /foo/)
end
end
context "with a class double" do
double(:dbl) do
# Ensure the original is never called.
abstract_stub def self.foo : Nil
end
abstract_stub def self.foo(arg) : Nil
end
abstract_stub def self.value : Int32
42
end
end
let(dbl) { class_double(:dbl) }
# Ensure invocations don't leak between examples.
pre_condition { expect(dbl).to_not have_received(:foo), "Leaked method calls from previous examples" }
# Ensure stubs don't leak between examples.
pre_condition do
expect { dbl.foo }.to raise_error(Spectator::UnexpectedMessage)
end
it "matches when a message is received" do
allow(dbl).to receive(:foo)
expect { dbl.foo }.to_not raise_error
end
it "returns the correct value" do
allow(dbl).to receive(:value).and_return(42)
expect(dbl.value).to eq(42)
end
it "matches when a message is received with matching arguments" do
allow(dbl).to receive(:foo).with(:bar)
expect { dbl.foo(:bar) }.to_not raise_error
end
it "raises when a message without arguments is received" do
allow(dbl).to receive(:foo).with(:bar)
expect { dbl.foo }.to raise_error(Spectator::UnexpectedMessage, /foo/)
end
it "raises when a message with different arguments is received" do
allow(dbl).to receive(:foo).with(:baz)
expect { dbl.foo(:bar) }.to raise_error(Spectator::UnexpectedMessage, /foo/)
end
end
context "with a mock" do
abstract class MyClass
abstract def foo : Int32
abstract def foo(arg) : Int32
end
mock(MyClass)
let(fake) { mock(MyClass) }
# Ensure invocations don't leak between examples.
pre_condition { expect(fake).to_not have_received(:foo), "Leaked method calls from previous examples" }
# Ensure stubs don't leak between examples.
pre_condition do
expect { fake.foo }.to raise_error(Spectator::UnexpectedMessage)
end
it "matches when a message is received" do
allow(fake).to receive(:foo).and_return(42)
expect(fake.foo).to eq(42)
end
it "returns the correct value" do
allow(fake).to receive(:foo).and_return(42)
expect(fake.foo).to eq(42)
end
it "matches when a message is received with matching arguments" do
allow(fake).to receive(:foo).with(:bar).and_return(42)
expect(fake.foo(:bar)).to eq(42)
end
it "raises when a message without arguments is received" do
allow(fake).to receive(:foo).with(:bar)
expect { fake.foo }.to raise_error(Spectator::UnexpectedMessage, /foo/)
end
it "raises when a message with different arguments is received" do
allow(fake).to receive(:foo).with(:baz)
expect { fake.foo(:bar) }.to raise_error(Spectator::UnexpectedMessage, /foo/)
end
end
context "with a class mock" do
class MyClass
def self.foo : Int32
42
end
def self.foo(arg) : Int32
42
end
end
mock(MyClass)
let(fake) { class_mock(MyClass) }
# Ensure invocations don't leak between examples.
pre_condition { expect(fake).to_not have_received(:foo), "Leaked method calls from previous examples" }
# Ensure stubs don't leak between examples.
pre_condition { expect(fake.foo).to eq(42) }
it "matches when a message is received" do
allow(fake).to receive(:foo).and_return(0)
expect(fake.foo).to eq(0)
end
it "returns the correct value" do
allow(fake).to receive(:foo).and_return(0)
expect(fake.foo).to eq(0)
end
it "matches when a message is received with matching arguments" do
allow(fake).to receive(:foo).with(:bar).and_return(0)
expect(fake.foo(:bar)).to eq(0)
end
it "calls the original when a message without arguments is received" do
allow(fake).to receive(:foo).with(:bar)
expect(fake.foo).to eq(42)
end
it "calls the original when a message with different arguments is received" do
allow(fake).to receive(:foo).with(:baz)
expect(fake.foo(:bar)).to eq(42)
end
end
end

View file

@ -1,385 +0,0 @@
require "../../../spec_helper"
Spectator.describe "Double DSL", :smoke do
context "specifying methods as keyword args" do
double(:test, foo: "foobar", bar: 42)
subject(dbl) { double(:test) }
it "defines a double with methods" do
aggregate_failures do
expect(dbl.foo).to eq("foobar")
expect(dbl.bar).to eq(42)
end
end
it "compiles types without unions" do
aggregate_failures do
expect(dbl.foo).to compile_as(String)
expect(dbl.bar).to compile_as(Int32)
end
end
context "with an unexpected message" do
it "raises an error" do
expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage, /baz/)
end
it "reports the double name" do
expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage, /:test/)
end
it "reports the arguments" do
expect { dbl.baz(:xyz, 123, a: "XYZ") }.to raise_error(Spectator::UnexpectedMessage, /\(:xyz, 123, a: "XYZ"\)/)
end
end
context "blocks" do
it "supports blocks" do
aggregate_failures do
expect(dbl.foo { nil }).to eq("foobar")
expect(dbl.bar { nil }).to eq(42)
end
end
it "supports blocks and has non-union return types" do
aggregate_failures do
expect(dbl.foo { nil }).to compile_as(String)
expect(dbl.bar { nil }).to compile_as(Int32)
end
end
it "fails on undefined messages" do
expect do
dbl.baz { nil }
end.to raise_error(Spectator::UnexpectedMessage, /baz/)
end
end
end
context "block with stubs" do
context "one method" do
double(:test2) do
stub def foo
"one method"
end
end
subject(dbl) { double(:test2) }
it "defines a double with methods" do
expect(dbl.foo).to eq("one method")
end
it "compiles types without unions" do
expect(dbl.foo).to compile_as(String)
end
end
context "two methods" do
double(:test3) do
stub def foo
"two methods"
end
stub def bar
42
end
end
subject(dbl) { double(:test3) }
it "defines a double with methods" do
aggregate_failures do
expect(dbl.foo).to eq("two methods")
expect(dbl.bar).to eq(42)
end
end
it "compiles types without unions" do
aggregate_failures do
expect(dbl.foo).to compile_as(String)
expect(dbl.bar).to compile_as(Int32)
end
end
end
context "empty block" do
double(:test4) do
end
subject(dbl) { double(:test4) }
it "defines a double" do
expect(dbl).to be_a(Spectator::Double)
end
end
context "stub-less method" do
double(:test5) do
def foo
"no stub"
end
end
subject(dbl) { double(:test5) }
it "defines a double with methods" do
expect(dbl.foo).to eq("no stub")
end
end
context "mixing keyword arguments" do
double(:test6, foo: "kwargs", bar: 42) do
stub def foo
"block"
end
stub def baz
"block"
end
stub def baz(value)
"block2"
end
end
subject(dbl) { double(:test6) }
it "overrides the keyword arguments with the block methods" do
expect(dbl.foo).to eq("block")
end
it "falls back to the keyword argument value for mismatched arguments" do
expect(dbl.foo(42)).to eq("kwargs")
end
it "can call methods defined only by keyword arguments" do
expect(dbl.bar).to eq(42)
end
it "can call methods defined only by the block" do
expect(dbl.baz).to eq("block")
end
it "can call methods defined by the block with different signatures" do
expect(dbl.baz(42)).to eq("block2")
end
end
context "methods accepting blocks" do
double(:test7) do
stub def foo(&)
yield
end
stub def bar(& : Int32 -> String)
yield 42
end
end
subject(dbl) { double(:test7) }
it "defines the method and yields" do
expect(dbl.foo { :xyz }).to eq(:xyz)
end
it "matches methods with block argument type restrictions" do
expect(dbl.bar &.to_s).to eq("42")
end
end
end
describe "double naming" do
double(:Name, type: :symbol)
it "accepts a symbolic double name" do
dbl = double(:Name)
expect(dbl.type).to eq(:symbol)
end
it "accepts a string double name" do
dbl = double("Name")
expect(dbl.type).to eq(:symbol)
end
it "accepts a constant double name" do
dbl = double(Name)
expect(dbl.type).to eq(:symbol)
end
end
describe "predefined method stubs" do
double(:test8, foo: 42)
let(dbl) { double(:test8, foo: 7) }
it "overrides the original value" do
expect(dbl.foo).to eq(7)
end
end
describe "scope" do
double(:outer, scope: :outer)
double(:scope, scope: :outer)
it "finds a double in the same scope" do
dbl = double(:outer)
expect(dbl.scope).to eq(:outer)
end
it "uses an identically named double from the same scope" do
dbl = double(:scope)
expect(dbl.scope).to eq(:outer)
end
context "inner1" do
double(:inner, scope: :inner1)
double(:scope, scope: :inner1)
it "finds a double in the same scope" do
dbl = double(:inner)
expect(dbl.scope).to eq(:inner1)
end
it "uses an identically named double from the same scope" do
dbl = double(:scope)
expect(dbl.scope).to eq(:inner1)
end
context "nested" do
it "finds a double from a parent scope" do
aggregate_failures do
dbl = double(:inner)
expect(dbl.scope).to eq(:inner1)
dbl = double(:outer)
expect(dbl.scope).to eq(:outer)
end
end
it "uses the inner-most identically named double" do
dbl = double(:inner)
expect(dbl.scope).to eq(:inner1)
end
end
end
context "inner2" do
double(:inner, scope: :inner2)
double(:scope, scope: :inner2)
it "finds a double in the same scope" do
dbl = double(:inner)
expect(dbl.scope).to eq(:inner2)
end
it "uses an identically named double from the same scope" do
dbl = double(:scope)
expect(dbl.scope).to eq(:inner2)
end
context "nested" do
it "finds a double from a parent scope" do
aggregate_failures do
dbl = double(:inner)
expect(dbl.scope).to eq(:inner2)
dbl = double(:outer)
expect(dbl.scope).to eq(:outer)
end
end
it "uses the inner-most identically named double" do
dbl = double(:inner)
expect(dbl.scope).to eq(:inner2)
end
end
end
end
describe "context" do
double(:context_double, predefined: :predefined, override: :predefined) do
stub abstract def memoize : Symbol
stub def inline : Symbol
:inline # Memoized values can't be used here.
end
stub def reference : String
memoize.to_s
end
end
let(memoize) { :memoize }
let(override) { :override }
let(dbl) { double(:context_double, override: override) }
before { allow(dbl).to receive(:memoize).and_return(memoize) }
it "doesn't change predefined values" do
expect(dbl.predefined).to eq(:predefined)
end
it "can use memoized values for overrides" do
expect(dbl.override).to eq(:override)
end
it "can use memoized values for stubs" do
expect(dbl.memoize).to eq(:memoize)
end
it "can override inline stubs" do
expect { allow(dbl).to receive(:inline).and_return(override) }.to change { dbl.inline }.from(:inline).to(:override)
end
it "can reference memoized values with indirection" do
expect { allow(dbl).to receive(:memoize).and_return(override) }.to change { dbl.reference }.from("memoize").to("override")
end
end
describe "class doubles" do
double(:class_double) do
abstract_stub def self.abstract_method
:abstract
end
stub def self.default_method
:default
end
stub def self.args(arg)
arg
end
stub def self.method1
:method1
end
stub def self.reference
method1.to_s
end
end
let(dbl) { class_double(:class_double) }
it "raises on abstract stubs" do
expect { dbl.abstract_method }.to raise_error(Spectator::UnexpectedMessage, /abstract_method/)
end
it "can define default stubs" do
expect(dbl.default_method).to eq(:default)
end
it "can define new stubs" do
expect { allow(dbl).to receive(:args).and_return(42) }.to change { dbl.args(5) }.from(5).to(42)
end
it "can override class method stubs" do
allow(dbl).to receive(:method1).and_return(:override)
expect(dbl.method1).to eq(:override)
end
it "can reference stubs" do
allow(dbl).to receive(:method1).and_return(:reference)
expect(dbl.reference).to eq("reference")
end
end
end

View file

@ -1,226 +0,0 @@
require "../../../spec_helper"
Spectator.describe "Deferred stub expectation DSL" do
context "with a double" do
double(:dbl) do
# Ensure the original is never called.
stub abstract def foo : Nil
stub abstract def foo(arg) : Nil
stub abstract def value : Int32
end
let(dbl) { double(:dbl) }
# Ensure invocations don't leak between examples.
pre_condition { expect(dbl).to_not have_received(:foo), "Leaked method calls from previous examples" }
# Ensure stubs don't leak between examples.
pre_condition do
expect { dbl.foo }.to raise_error(Spectator::UnexpectedMessage)
dbl._spectator_clear_calls # Don't include previous call in results.
end
it "matches when a message is received" do
expect(dbl).to receive(:foo)
dbl.foo
end
it "returns the correct value" do
expect(dbl).to receive(:value).and_return(42)
expect(dbl.value).to eq(42)
end
it "matches when a message isn't received" do
expect(dbl).to_not receive(:foo)
end
it "matches when a message is received with matching arguments" do
expect(dbl).to receive(:foo).with(:bar)
dbl.foo(:bar)
end
it "matches when a message without arguments is received" do
expect(dbl).to_not receive(:foo).with(:bar)
dbl.foo
end
it "matches when a message without arguments isn't received" do
expect(dbl).to_not receive(:foo).with(:bar)
end
it "matches when a message with arguments isn't received" do
expect(dbl).to_not receive(:foo).with(:baz)
dbl.foo(:bar)
end
end
context "with a class double" do
double(:dbl) do
# Ensure the original is never called.
abstract_stub def self.foo : Nil
end
abstract_stub def self.foo(arg) : Nil
end
abstract_stub def self.value : Int32
42
end
end
let(dbl) { class_double(:dbl) }
# Ensure invocations don't leak between examples.
pre_condition { expect(dbl).to_not have_received(:foo), "Leaked method calls from previous examples" }
# Ensure stubs don't leak between examples.
pre_condition do
expect { dbl.foo }.to raise_error(Spectator::UnexpectedMessage)
dbl._spectator_clear_calls # Don't include previous call in results.
end
it "matches when a message is received" do
expect(dbl).to receive(:foo)
dbl.foo
end
it "returns the correct value" do
expect(dbl).to receive(:value).and_return(42)
expect(dbl.value).to eq(42)
end
it "matches when a message isn't received" do
expect(dbl).to_not receive(:foo)
end
it "matches when a message is received with matching arguments" do
expect(dbl).to receive(:foo).with(:bar)
dbl.foo(:bar)
end
it "matches when a message without arguments is received" do
expect(dbl).to_not receive(:foo).with(:bar)
dbl.foo
end
it "matches when a message without arguments isn't received" do
expect(dbl).to_not receive(:foo).with(:bar)
end
it "matches when a message with arguments isn't received" do
expect(dbl).to_not receive(:foo).with(:baz)
dbl.foo(:bar)
end
end
context "with a mock" do
abstract class MyClass
abstract def foo : Int32
abstract def foo(arg) : Int32
end
mock(MyClass)
let(fake) { mock(MyClass) }
# Ensure invocations don't leak between examples.
pre_condition { expect(fake).to_not have_received(:foo), "Leaked method calls from previous examples" }
# Ensure stubs don't leak between examples.
pre_condition do
expect { fake.foo }.to raise_error(Spectator::UnexpectedMessage)
fake._spectator_clear_calls # Don't include previous call in results.
end
it "matches when a message is received" do
expect(fake).to receive(:foo).and_return(42)
fake.foo(:bar)
end
it "returns the correct value" do
expect(fake).to receive(:foo).and_return(42)
expect(fake.foo).to eq(42)
end
it "matches when a message isn't received" do
expect(fake).to_not receive(:foo)
end
it "matches when a message is received with matching arguments" do
expect(fake).to receive(:foo).with(:bar).and_return(42)
fake.foo(:bar)
end
it "matches when a message without arguments is received" do
expect(fake).to_not receive(:foo).with(:bar).and_return(42)
fake.foo
end
it "matches when a message without arguments is received" do
expect(fake).to_not receive(:foo).with(:bar)
end
it "matches when a message with arguments isn't received" do
expect(fake).to_not receive(:foo).with(:baz).and_return(42)
fake.foo(:bar)
end
end
context "with a class mock" do
class MyClass
def self.foo : Int32
42
end
def self.foo(arg) : Int32
42
end
end
mock(MyClass)
let(fake) { class_mock(MyClass) }
# Ensure invocations don't leak between examples.
pre_condition { expect(fake).to_not have_received(:foo), "Leaked method calls from previous examples" }
# Ensure stubs don't leak between examples.
pre_condition do
expect(fake.foo).to eq(42)
fake._spectator_clear_calls # Don't include previous call in results.
end
it "matches when a message is received" do
expect(fake).to receive(:foo).and_return(0)
fake.foo(:bar)
end
it "returns the correct value" do
expect(fake).to receive(:foo).and_return(0)
expect(fake.foo).to eq(0)
end
it "matches when a message isn't received" do
expect(fake).to_not receive(:foo)
end
it "matches when a message is received with matching arguments" do
expect(fake).to receive(:foo).with(:bar).and_return(0)
fake.foo(:bar)
end
it "matches when a message without arguments is received" do
expect(fake).to_not receive(:foo).with(:bar).and_return(0)
fake.foo
end
it "matches when a message without arguments is received" do
expect(fake).to_not receive(:foo).with(:bar)
end
it "matches when a message with arguments isn't received" do
expect(fake).to_not receive(:foo).with(:baz).and_return(0)
fake.foo(:bar)
end
end
end

View file

@ -1,339 +0,0 @@
require "../../../spec_helper"
Spectator.describe "Stubbable receiver DSL" do
context "with a double" do
double(:dbl, foo: 42)
let(dbl) { double(:dbl) }
# Ensure invocations don't leak between examples.
pre_condition { expect(dbl).to_not have_received(:foo), "Leaked method calls from previous examples" }
it "matches when a message is received" do
dbl.foo
expect(dbl).to have_received(:foo)
end
it "matches when a message isn't received" do
expect(dbl).to_not have_received(:foo)
end
it "matches when a message is received with matching arguments" do
dbl.foo(:bar)
expect(dbl).to have_received(:foo).with(:bar)
end
it "matches when a message without arguments is received" do
dbl.foo
expect(dbl).to_not have_received(:foo).with(:bar)
end
it "matches when a message without arguments isn't received" do
expect(dbl).to_not have_received(:foo).with(:bar)
end
it "matches when a message with arguments isn't received" do
dbl.foo(:bar)
expect(dbl).to_not have_received(:foo).with(:baz)
end
end
context "with a class double" do
double(:dbl) do
stub def self.foo
42
end
stub def self.foo(arg)
42
end
end
let(dbl) { class_double(:dbl) }
# Ensure invocations don't leak between examples.
pre_condition { expect(dbl).to_not have_received(:foo), "Leaked method calls from previous examples" }
it "matches when a message is received" do
dbl.foo
expect(dbl).to have_received(:foo)
end
it "matches when a message isn't received" do
expect(dbl).to_not have_received(:foo)
end
it "matches when a message is received with matching arguments" do
dbl.foo(:bar)
expect(dbl).to have_received(:foo).with(:bar)
end
it "matches when a message without arguments is received" do
dbl.foo
expect(dbl).to_not have_received(:foo).with(:bar)
end
it "matches when a message without arguments isn't received" do
expect(dbl).to_not have_received(:foo).with(:bar)
end
it "matches when a message with arguments isn't received" do
dbl.foo(:bar)
expect(dbl).to_not have_received(:foo).with(:baz)
end
end
context "with a mock" do
abstract class MyClass
abstract def foo(arg) : Int32
end
mock(MyClass, foo: 42)
let(fake) { mock(MyClass) }
# Ensure invocations don't leak between examples.
pre_condition { expect(fake).to_not have_received(:foo), "Leaked method calls from previous examples" }
it "matches when a message is received" do
fake.foo(:bar)
expect(fake).to have_received(:foo)
end
it "matches when a message isn't received" do
expect(fake).to_not have_received(:foo)
end
it "matches when a message is received with matching arguments" do
fake.foo(:bar)
expect(fake).to have_received(:foo).with(:bar)
end
it "matches when a message without arguments is received" do
expect(fake).to_not have_received(:foo).with(:bar)
end
it "matches when a message with arguments isn't received" do
fake.foo(:bar)
expect(fake).to_not have_received(:foo).with(:baz)
end
end
context "with a class mock" do
class MyClass
def self.foo(arg) : Int32
42
end
end
mock(MyClass)
let(fake) { class_mock(MyClass) }
# Ensure invocations don't leak between examples.
pre_condition { expect(fake).to_not have_received(:foo), "Leaked method calls from previous examples" }
it "matches when a message is received" do
fake.foo(:bar)
expect(fake).to have_received(:foo)
end
it "matches when a message isn't received" do
expect(fake).to_not have_received(:foo)
end
it "matches when a message is received with matching arguments" do
fake.foo(:bar)
expect(fake).to have_received(:foo).with(:bar)
end
it "matches when a message without arguments is received" do
expect(fake).to_not have_received(:foo).with(:bar)
end
it "matches when a message with arguments isn't received" do
fake.foo(:bar)
expect(fake).to_not have_received(:foo).with(:baz)
end
end
context "count modifiers" do
double(:dbl, foo: 42)
let(dbl) { double(:dbl) }
describe "#once" do
it "matches when the stub is called once" do
dbl.foo
expect(dbl).to have_received(:foo).once
end
it "doesn't match when the stub isn't called" do
expect(dbl).to_not have_received(:foo).once
end
it "doesn't match when the stub is called twice" do
2.times { dbl.foo }
expect(dbl).to_not have_received(:foo).once
end
end
describe "#twice" do
it "matches when the stub is called twice" do
2.times { dbl.foo }
expect(dbl).to have_received(:foo).twice
end
it "doesn't match when the stub isn't called" do
expect(dbl).to_not have_received(:foo).twice
end
it "doesn't match when the stub is called once" do
dbl.foo
expect(dbl).to_not have_received(:foo).twice
end
it "doesn't match when the stub is called thrice" do
3.times { dbl.foo }
expect(dbl).to_not have_received(:foo).twice
end
end
describe "#exactly" do
it "matches when the stub is called the exact amount" do
3.times { dbl.foo }
expect(dbl).to have_received(:foo).exactly(3).times
end
it "doesn't match when the stub isn't called" do
expect(dbl).to_not have_received(:foo).exactly(3).times
end
it "doesn't match when the stub is called less than the amount" do
2.times { dbl.foo }
expect(dbl).to_not have_received(:foo).exactly(3).times
end
it "doesn't match when the stub is called more than the amount" do
4.times { dbl.foo }
expect(dbl).to_not have_received(:foo).exactly(3).times
end
end
describe "#at_least" do
it "matches when the stub is called the exact amount" do
3.times { dbl.foo }
expect(dbl).to have_received(:foo).at_least(3).times
end
it "doesn't match when the stub isn't called" do
expect(dbl).to_not have_received(:foo).at_least(3).times
end
it "doesn't match when the stub is called less than the amount" do
2.times { dbl.foo }
expect(dbl).to_not have_received(:foo).at_least(3).times
end
it "matches when the stub is called more than the amount" do
4.times { dbl.foo }
expect(dbl).to have_received(:foo).at_least(3).times
end
end
describe "#at_most" do
it "matches when the stub is called the exact amount" do
3.times { dbl.foo }
expect(dbl).to have_received(:foo).at_most(3).times
end
it "matches when the stub isn't called" do
expect(dbl).to have_received(:foo).at_most(3).times
end
it "matches when the stub is called less than the amount" do
2.times { dbl.foo }
expect(dbl).to have_received(:foo).at_most(3).times
end
it "doesn't match when the stub is called more than the amount" do
4.times { dbl.foo }
expect(dbl).to_not have_received(:foo).at_most(3).times
end
end
describe "#at_least_once" do
it "matches when the stub is called once" do
dbl.foo
expect(dbl).to have_received(:foo).at_least_once
end
it "doesn't match when the stub isn't called" do
expect(dbl).to_not have_received(:foo).at_least_once
end
it "matches when the stub is called more than once" do
2.times { dbl.foo }
expect(dbl).to have_received(:foo).at_least_once
end
end
describe "#at_least_twice" do
it "doesn't match when the stub is called once" do
dbl.foo
expect(dbl).to_not have_received(:foo).at_least_twice
end
it "doesn't match when the stub isn't called" do
expect(dbl).to_not have_received(:foo).at_least_twice
end
it "matches when the stub is called twice" do
2.times { dbl.foo }
expect(dbl).to have_received(:foo).at_least_twice
end
it "matches when the stub is called more than twice" do
3.times { dbl.foo }
expect(dbl).to have_received(:foo).at_least_twice
end
end
describe "#at_most_once" do
it "matches when the stub is called once" do
dbl.foo
expect(dbl).to have_received(:foo).at_most_once
end
it "matches when the stub isn't called" do
expect(dbl).to have_received(:foo).at_most_once
end
it "doesn't match when the stub is called more than once" do
2.times { dbl.foo }
expect(dbl).to_not have_received(:foo).at_most_once
end
end
describe "#at_most_twice" do
it "matches when the stub is called once" do
dbl.foo
expect(dbl).to have_received(:foo).at_most_twice
end
it "matches when the stub isn't called" do
expect(dbl).to have_received(:foo).at_most_twice
end
it "matches when the stub is called twice" do
2.times { dbl.foo }
expect(dbl).to have_received(:foo).at_most_twice
end
it "doesn't match when the stub is called more than twice" do
3.times { dbl.foo }
expect(dbl).to_not have_received(:foo).at_most_twice
end
end
end
end

View file

@ -1,83 +0,0 @@
require "../../../spec_helper"
Spectator.describe "Lazy double DSL" do
context "specifying methods as keyword args" do
subject(dbl) { double(:test, foo: "foobar", bar: 42) }
it "defines a double with methods" do
aggregate_failures do
expect(dbl.foo).to eq("foobar")
expect(dbl.bar).to eq(42)
end
end
context "with an unexpected message" do
it "raises an error" do
expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage, /baz/)
end
it "reports the double name" do
expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage, /:test/)
end
it "reports the arguments" do
expect { dbl.baz(:xyz, 123, a: "XYZ") }.to raise_error(Spectator::UnexpectedMessage, /\(:xyz, 123, a: "XYZ"\)/)
end
end
context "blocks" do
it "supports blocks" do
aggregate_failures do
expect(dbl.foo { nil }).to eq("foobar")
expect(dbl.bar { nil }).to eq(42)
end
end
it "fails on undefined messages" do
expect do
dbl.baz { nil }
end.to raise_error(Spectator::UnexpectedMessage, /baz/)
end
end
end
describe "double naming" do
it "accepts a symbolic double name" do
dbl = double(:name)
expect { dbl.oops }.to raise_error(Spectator::UnexpectedMessage, /:name/)
end
it "accepts a string double name" do
dbl = double("Name")
expect { dbl.oops }.to raise_error(Spectator::UnexpectedMessage, /"Name"/)
end
it "accepts no name" do
dbl = double
expect { dbl.oops }.to raise_error(Spectator::UnexpectedMessage, /anonymous/i)
end
it "accepts no name and predefined responses" do
dbl = double(foo: 42)
expect(dbl.foo).to eq(42)
end
end
describe "context" do
let(memoize) { :memoize }
let(override) { :override }
let(dbl) { double(predefined: :predefined, memoize: memoize) }
it "doesn't change predefined values" do
expect(dbl.predefined).to eq(:predefined)
end
it "can use memoized values for stubs" do
expect(dbl.memoize).to eq(:memoize)
end
it "can stub methods with memoized values" do
expect { allow(dbl).to receive(:memoize).and_return(override) }.to change { dbl.memoize }.from(:memoize).to(:override)
end
end
end

File diff suppressed because it is too large Load diff

View file

@ -1,189 +0,0 @@
require "../../../spec_helper"
Spectator.describe "Null double DSL" do
context "specifying methods as keyword args" do
double(:test, foo: "foobar", bar: 42)
subject(dbl) { double(:test).as_null_object }
it "defines a double with methods" do
aggregate_failures do
expect(dbl.foo).to eq("foobar")
expect(dbl.bar).to eq(42)
end
end
it "compiles types without unions" do
aggregate_failures do
expect(dbl.foo).to compile_as(String)
expect(dbl.bar).to compile_as(Int32)
end
end
it "returns self for unexpected messages" do
expect(dbl.baz).to be(dbl)
end
context "blocks" do
it "supports blocks" do
aggregate_failures do
expect(dbl.foo { nil }).to eq("foobar")
expect(dbl.bar { nil }).to eq(42)
end
end
it "supports blocks and has non-union return types" do
aggregate_failures do
expect(dbl.foo { nil }).to compile_as(String)
expect(dbl.bar { nil }).to compile_as(Int32)
end
end
it "returns self on undefined messages" do
expect(dbl.baz { nil }).to be(dbl)
end
end
end
context "block with stubs" do
context "one method" do
double(:test2) do
stub def foo
"one method"
end
end
subject(dbl) { double(:test2).as_null_object }
it "defines a double with methods" do
expect(dbl.foo).to eq("one method")
end
it "compiles types without unions" do
expect(dbl.foo).to compile_as(String)
end
end
context "two methods" do
double(:test3) do
stub def foo
"two methods"
end
stub def bar
42
end
end
subject(dbl) { double(:test3).as_null_object }
it "defines a double with methods" do
aggregate_failures do
expect(dbl.foo).to eq("two methods")
expect(dbl.bar).to eq(42)
end
end
it "compiles types without unions" do
aggregate_failures do
expect(dbl.foo).to compile_as(String)
expect(dbl.bar).to compile_as(Int32)
end
end
end
context "empty block" do
double(:test4) do
end
subject(dbl) { double(:test4).as_null_object }
it "defines a double" do
expect(dbl).to be_a(Spectator::Double)
end
end
context "stub-less method" do
double(:test5) do
def foo
"no stub"
end
end
subject(dbl) { double(:test5).as_null_object }
it "defines a double with methods" do
expect(dbl.foo).to eq("no stub")
end
end
context "mixing keyword arguments" do
double(:test6, foo: "kwargs", bar: 42) do
stub def foo
"block"
end
stub def baz
"block"
end
stub def baz(value)
"block2"
end
end
subject(dbl) { double(:test6).as_null_object }
it "overrides the keyword arguments with the block methods" do
expect(dbl.foo).to eq("block")
end
it "falls back to the keyword argument value for mismatched arguments" do
expect(dbl.foo(42)).to eq("kwargs")
end
it "can call methods defined only by keyword arguments" do
expect(dbl.bar).to eq(42)
end
it "can call methods defined only by the block" do
expect(dbl.baz).to eq("block")
end
it "can call methods defined by the block with different signatures" do
expect(dbl.baz(42)).to eq("block2")
end
end
context "methods accepting blocks" do
double(:test7) do
stub def foo(&)
yield
end
stub def bar(& : Int32 -> String)
yield 42
end
end
subject(dbl) { double(:test7).as_null_object }
it "defines the method and yields" do
expect(dbl.foo { :xyz }).to eq(:xyz)
end
it "matches methods with block argument type restrictions" do
expect(dbl.bar &.to_s).to eq("42")
end
end
end
describe "predefined method stubs" do
double(:test8, foo: 42)
let(dbl) { double(:test8, foo: 7).as_null_object }
it "overrides the original value" do
expect(dbl.foo).to eq(7)
end
end
end

View file

@ -1,116 +0,0 @@
require "../../../spec_helper"
Spectator.describe "Stub DSL", :smoke do
double(:foobar, foo: 42, bar: "baz") do
stub abstract def other : String
stub abstract def null : Nil
end
let(dbl) { double(:foobar) }
it "overrides default stubs" do
allow(dbl).to receive(:foo).and_return(123)
expect(dbl.foo).to eq(123)
end
it "overrides abstract stubs" do
allow(dbl).to receive(:other).and_return("test")
expect(dbl.other).to eq("test")
end
it "returns nil by default" do
allow(dbl).to receive(:null)
expect(dbl.null).to be_nil
end
it "raises on cast errors" do
allow(dbl).to receive(:foo).and_return(:xyz)
expect { dbl.foo }.to raise_error(TypeCastError, /Int32/)
end
describe "#receive" do
it "returns the value from the block" do
allow(dbl).to receive(:foo) { 5 }
expect(dbl.foo).to eq(5)
end
it "accepts and calls block" do
count = 0
allow(dbl).to receive(:foo) { count += 1 }
expect { dbl.foo }.to change { count }.from(0).to(1)
end
it "passes the arguments to the block" do
captured = nil.as(Spectator::AbstractArguments?)
allow(dbl).to receive(:foo) { |a| captured = a; 0 }
dbl.foo(3, 5, 7, bar: "baz")
args = Spectator::Arguments.capture(3, 5, 7, bar: "baz")
expect(captured).to eq(args)
end
end
describe "#with" do
context Spectator::MultiValueStub do
it "applies the stub with matching arguments" do
allow(dbl).to receive(:foo).and_return(1, 2, 3).with(Int32, bar: /baz/)
aggregate_failures do
expect(dbl.foo(3, bar: "foobarbaz")).to eq(1)
expect(dbl.foo).to eq(42)
expect(dbl.foo(5, bar: "barbaz")).to eq(2)
expect(dbl.foo(7, bar: "foobaz")).to eq(3)
expect(dbl.foo(11, bar: "baz")).to eq(3)
end
end
end
context Spectator::NullStub do
it "applies the stub with matching arguments" do
allow(dbl).to receive(:foo).with(Int32, bar: /baz/).and_return(1)
aggregate_failures do
expect(dbl.foo(3, bar: "foobarbaz")).to eq(1)
expect(dbl.foo).to eq(42)
end
end
it "changes to a proc stub" do
called = 0
allow(dbl).to receive(:foo).with(Int32, bar: /baz/) { called += 1 }
aggregate_failures do
expect { dbl.foo(3, bar: "foobarbaz") }.to change { called }.from(0).to(1)
expect(dbl.foo(5, bar: "baz")).to eq(2)
expect(dbl.foo).to eq(42)
end
end
end
context Spectator::ValueStub do
it "applies the stub with matching arguments" do
allow(dbl).to receive(:foo).and_return(1).with(Int32, bar: /baz/)
aggregate_failures do
expect(dbl.foo(3, bar: "foobarbaz")).to eq(1)
expect(dbl.foo).to eq(42)
end
end
end
end
describe "#no_args" do
it "defines a stub with a no arguments constraint" do
allow(dbl).to receive(:foo).with(no_args).and_return(5)
aggregate_failures do
expect(dbl.foo).to eq(5)
expect(dbl.foo(0)).to eq(42)
end
end
end
describe "#any_args" do
it "defines a stub with no arguments constraint" do
allow(dbl).to receive(:foo).with(any_args).and_return(5)
aggregate_failures do
expect(dbl.foo).to eq(5)
expect(dbl.foo(0)).to eq(5)
end
end
end
end

View file

@ -1,15 +0,0 @@
require "../spec_helper"
Spectator.describe Spectator::Lazy do
it "returns the value of the block" do
lazy = Spectator::Lazy(Int32).new
expect { lazy.get { 42 } }.to eq(42)
end
it "caches the value" do
lazy = Spectator::Lazy(Int32).new
count = 0
expect { lazy.get { count += 1 } }.to change { count }.from(0).to(1)
expect { lazy.get { count += 1 } }.to_not change { count }
end
end

View file

@ -1,28 +0,0 @@
require "../spec_helper"
Spectator.describe Spectator::LazyWrapper do
it "returns the value of the block" do
expect { subject.get { 42 } }.to eq(42)
end
it "caches the value" do
wrapper = described_class.new
count = 0
expect { wrapper.get { count += 1 } }.to change { count }.from(0).to(1)
expect { wrapper.get { count += 1 } }.to_not change { count }
end
# This type of nesting is used when `super` is called in a subject block.
# ```
# subject { super.to_s }
# ```
it "works with nested wrappers" do
outer = described_class.new
inner = described_class.new
value = outer.get do
inner.get { 42 }.to_s
end
expect(value).to eq("42")
expect(value).to be_a(String)
end
end

View file

@ -1,39 +0,0 @@
require "../../spec_helper"
Spectator.describe Spectator::Allow do
let(dbl) { Spectator::LazyDouble.new(foo: 42) }
let(stub) { Spectator::ValueStub.new(:foo, 123) }
subject(alw) { Spectator::Allow.new(dbl) }
describe "#to" do
it "applies a stub" do
expect { alw.to(stub) }.to change { dbl.foo }.from(42).to(123)
end
context "leak" do
class Thing
def foo
42
end
end
mock Thing
getter(thing : Thing) { mock(Thing) }
# Workaround type restrictions requiring a constant.
def fake
class_mock(Thing).cast(thing)
end
specify do
expect { allow(fake).to(stub) }.to change { fake.foo }.from(42).to(123)
end
# This example must be run after the previous (random order may break this).
it "clears the stub after the example completes" do
expect { fake.foo }.to eq(42)
end
end
end
end

View file

@ -1,284 +0,0 @@
require "../../spec_helper"
Spectator.describe Spectator::Arguments do
subject(arguments) { Spectator::Arguments.new({42, "foo"}, {bar: "baz", qux: 123}) }
it "stores the arguments" do
expect(arguments).to have_attributes(
args: {42, "foo"},
kwargs: {bar: "baz", qux: 123}
)
end
describe ".capture" do
subject { Spectator::Arguments.capture(42, "foo", bar: "baz", qux: 123) }
it "stores the arguments and keyword arguments" do
is_expected.to have_attributes(args: {42, "foo"}, kwargs: {bar: "baz", qux: 123})
end
end
describe "#[](index)" do
it "returns a positional argument" do
aggregate_failures do
expect(arguments[0]).to eq(42)
expect(arguments[1]).to eq("foo")
end
end
end
describe "#[](symbol)" do
it "returns a keyword argument" do
aggregate_failures do
expect(arguments[:bar]).to eq("baz")
expect(arguments[:qux]).to eq(123)
end
end
end
describe "#to_s" do
subject { arguments.to_s }
it "formats the arguments" do
is_expected.to eq("(42, \"foo\", bar: \"baz\", qux: 123)")
end
context "when empty" do
let(arguments) { Spectator::Arguments.none }
it "returns (no args)" do
is_expected.to eq("(no args)")
end
end
end
describe "#==" do
subject { arguments == other }
context "with Arguments" do
context "with equal arguments" do
let(other) { arguments }
it { is_expected.to be_true }
end
context "with different arguments" do
let(other) { Spectator::Arguments.new({123, :foo, "bar"}, {opt: "foobar"}) }
it { is_expected.to be_false }
end
context "with the same kwargs in a different order" do
let(other) { Spectator::Arguments.new(arguments.args, {qux: 123, bar: "baz"}) }
it { is_expected.to be_true }
end
context "with a missing kwarg" do
let(other) { Spectator::Arguments.new(arguments.args, {bar: "baz"}) }
it { is_expected.to be_false }
end
context "with an extra kwarg" do
let(other) { Spectator::Arguments.new(arguments.args, {bar: "baz", qux: 123, extra: 0}) }
it { is_expected.to be_false }
end
end
context "with FormalArguments" do
context "with equal arguments" do
let(other) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, {bar: "baz", qux: 123}) }
it { is_expected.to be_true }
end
context "with different arguments" do
let(other) { Spectator::FormalArguments.new({arg1: 123, arg2: :foo, arg3: "bar"}, {opt: "foobar"}) }
it { is_expected.to be_false }
end
context "with the same kwargs in a different order" do
let(other) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, {qux: 123, bar: "baz"}) }
it { is_expected.to be_true }
end
context "with a missing kwarg" do
let(other) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, {bar: "baz"}) }
it { is_expected.to be_false }
end
context "with an extra kwarg" do
let(other) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, {bar: "baz", qux: 123, extra: 0}) }
it { is_expected.to be_false }
end
context "with different splat arguments" do
let(other) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {1, 2, 3}, {bar: "baz", qux: 123}) }
it { is_expected.to be_false }
end
context "with mixed positional tuple types" do
let(other) { Spectator::FormalArguments.new({arg1: 42}, :splat, {"foo"}, {bar: "baz", qux: 123}) }
it { is_expected.to be_true }
end
end
end
describe "#===" do
subject { pattern === arguments }
context "with Arguments" do
context "with equal arguments" do
let(pattern) { arguments }
it { is_expected.to be_true }
end
context "with matching arguments" do
let(pattern) { Spectator::Arguments.new({Int32, /foo/}, {bar: /baz/, qux: Int32}) }
it { is_expected.to be_true }
end
context "with non-matching arguments" do
let(pattern) { Spectator::Arguments.new({Float64, /bar/}, {bar: /foo/, qux: "123"}) }
it { is_expected.to be_false }
end
context "with different arguments" do
let(pattern) { Spectator::Arguments.new({123, :foo, "bar"}, {opt: "foobar"}) }
it { is_expected.to be_false }
end
context "with the same kwargs in a different order" do
let(pattern) { Spectator::Arguments.new(arguments.args, {qux: Int32, bar: /baz/}) }
it { is_expected.to be_true }
end
context "with an additional kwarg" do
let(pattern) { Spectator::Arguments.new(arguments.args, {bar: /baz/}) }
it { is_expected.to be_true }
end
context "with a missing kwarg" do
let(pattern) { Spectator::Arguments.new(arguments.args, {bar: /baz/, qux: Int32, extra: 0}) }
it { is_expected.to be_false }
end
end
context "with FormalArguments" do
let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, {bar: "baz", qux: 123}) }
context "with equal arguments" do
let(pattern) { Spectator::Arguments.new({42, "foo"}, {bar: "baz", qux: 123}) }
it { is_expected.to be_true }
end
context "with matching arguments" do
let(pattern) { Spectator::Arguments.new({Int32, /foo/}, {bar: /baz/, qux: Int32}) }
it { is_expected.to be_true }
end
context "with non-matching arguments" do
let(pattern) { Spectator::Arguments.new({Float64, /bar/}, {bar: /foo/, qux: "123"}) }
it { is_expected.to be_false }
end
context "with different arguments" do
let(pattern) { Spectator::Arguments.new({123, :foo, "bar"}, {opt: "foobar"}) }
it { is_expected.to be_false }
end
context "with the same kwargs in a different order" do
let(pattern) { Spectator::Arguments.new(arguments.positional, {qux: Int32, bar: /baz/}) }
it { is_expected.to be_true }
end
context "with an additional kwarg" do
let(pattern) { Spectator::Arguments.new(arguments.positional, {bar: /baz/}) }
it { is_expected.to be_true }
end
context "with a missing kwarg" do
let(pattern) { Spectator::Arguments.new(arguments.positional, {bar: /baz/, qux: Int32, extra: 0}) }
it { is_expected.to be_false }
end
context "with different splat arguments" do
let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {1, 2, 3}, super.kwargs) }
let(pattern) { Spectator::Arguments.new({Int32, /foo/, 5}, arguments.kwargs) }
it { is_expected.to be_false }
end
context "with matching mixed positional tuple types" do
let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {1, 2, 3}, super.kwargs) }
let(pattern) { Spectator::Arguments.new({Int32, /foo/, 1, 2, 3}, arguments.kwargs) }
it { is_expected.to be_true }
end
context "with non-matching mixed positional tuple types" do
let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {1, 2, 3}, super.kwargs) }
let(pattern) { Spectator::Arguments.new({Float64, /bar/, 3, 2, Symbol}, arguments.kwargs) }
it { is_expected.to be_false }
end
context "with matching args spilling over into splat and mixed positional tuple types" do
let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
let(pattern) { Spectator::Arguments.capture(Int32, /foo/, Symbol, Symbol, :z, bar: /baz/, qux: Int32) }
it { is_expected.to be_true }
end
context "with non-matching args spilling over into splat and mixed positional tuple types" do
let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
let(pattern) { Spectator::Arguments.capture(Float64, /bar/, Symbol, String, :z, bar: /foo/, qux: Int32) }
it { is_expected.to be_false }
end
context "with matching mixed named positional and keyword arguments" do
let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
let(pattern) { Spectator::Arguments.capture(/foo/, Symbol, :y, Symbol, arg1: Int32, bar: /baz/, qux: 123) }
it { is_expected.to be_true }
end
context "with non-matching mixed named positional and keyword arguments" do
let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
let(pattern) { Spectator::Arguments.capture(5, Symbol, :z, Symbol, arg2: /foo/, bar: /baz/, qux: Int32) }
it { is_expected.to be_false }
end
context "with non-matching mixed named positional and keyword arguments" do
let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
let(pattern) { Spectator::Arguments.capture(/bar/, String, :y, Symbol, arg1: 0, bar: /foo/, qux: Float64) }
it { is_expected.to be_false }
end
end
end
end

View file

@ -1,542 +0,0 @@
require "../../spec_helper"
Spectator.describe Spectator::Double do
Spectator::Double.define(EmptyDouble)
Spectator::Double.define(FooBarDouble, "dbl-name", foo: 42, bar: "baz")
# The subject `dbl` must be carefully used in sub-contexts, otherwise it pollutes parent scopes.
# This changes the type of `dbl` to `Double+`, which produces a union of methods and their return types.
context "plain double" do
subject(dbl) { FooBarDouble.new }
it "responds to defined messages" do
aggregate_failures do
expect(dbl.foo).to eq(42)
expect(dbl.bar).to eq("baz")
end
end
it "fails on undefined messages" do
expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage, /baz/)
end
it "reports the name in errors" do
expect { dbl.baz }.to raise_error(/"dbl-name"/)
end
it "reports arguments" do
expect { dbl.baz(123, "qux", field: :value) }.to raise_error(Spectator::UnexpectedMessage, /\(123, "qux", field: :value\)/)
end
it "has a non-union return type" do
aggregate_failures do
expect(dbl.foo).to compile_as(Int32)
expect(dbl.bar).to compile_as(String)
end
end
it "uses nil for undefined messages" do
expect { dbl.baz }.to compile_as(Nil)
end
context "blocks" do
it "supports blocks" do
aggregate_failures do
expect(dbl.foo { nil }).to eq(42)
expect(dbl.bar { nil }).to eq("baz")
end
end
it "supports blocks and has non-union return types" do
aggregate_failures do
expect(dbl.foo { nil }).to compile_as(Int32)
expect(dbl.bar { nil }).to compile_as(String)
end
end
it "fails on undefined messages" do
expect do
dbl.baz { nil }
end.to raise_error(Spectator::UnexpectedMessage, /baz/)
end
end
end
context "without a double name" do
Spectator::Double.define(NamelessDouble, foo: 42)
subject(dbl) { NamelessDouble.new }
it "reports as anonymous" do
expect { dbl.baz }.to raise_error(/anonymous/i)
end
end
context "with abstract stubs and return type annotations" do
Spectator::Double.define(TestDouble) do
abstract_stub abstract def foo(value) : String
end
let(arguments) { Spectator::Arguments.capture(/foo/) }
let(stub) { Spectator::ValueStub.new(:foo, "bar", arguments).as(Spectator::Stub) }
subject(dbl) { TestDouble.new([stub]) }
it "enforces the return type" do
expect(dbl.foo("foobar")).to compile_as(String)
end
it "raises on non-matching arguments" do
expect { dbl.foo("bar") }.to raise_error(Spectator::UnexpectedMessage, /foo/)
end
it "raises on non-matching stub" do
stub = Spectator::ValueStub.new(:foo, 42, arguments).as(Spectator::Stub)
dbl._spectator_define_stub(stub)
expect { dbl.foo("foobar") }.to raise_error(TypeCastError, /String/)
end
end
context "with nillable return type annotations" do
Spectator::Double.define(TestDouble) do
abstract_stub abstract def foo : String?
abstract_stub abstract def bar : Nil
end
let(foo_stub) { Spectator::ValueStub.new(:foo, nil).as(Spectator::Stub) }
let(bar_stub) { Spectator::ValueStub.new(:bar, nil).as(Spectator::Stub) }
subject(dbl) { TestDouble.new([foo_stub, bar_stub]) }
it "doesn't raise on nil" do
aggregate_failures do
expect(dbl.foo).to be_nil
expect(dbl.bar).to be_nil
end
end
end
context "with a method that uses NoReturn" do
Spectator::Double.define(NoReturnDouble) do
abstract_stub abstract def oops : NoReturn
end
subject(dbl) { NoReturnDouble.new }
it "raises a TypeCastError when using a value-based stub" do
stub = Spectator::ValueStub.new(:oops, nil).as(Spectator::Stub)
dbl._spectator_define_stub(stub)
expect { dbl.oops }.to raise_error(TypeCastError, /NoReturn/)
end
it "raises when using an exception stub" do
exception = ArgumentError.new("bogus")
stub = Spectator::ExceptionStub.new(:oops, exception).as(Spectator::Stub)
dbl._spectator_define_stub(stub)
expect { dbl.oops }.to raise_error(ArgumentError, "bogus")
end
end
context "with common object methods" do
subject(dbl) do
EmptyDouble.new([
Spectator::ValueStub.new(:"!=", false),
Spectator::ValueStub.new(:"!~", false),
Spectator::ValueStub.new(:"==", true),
Spectator::ValueStub.new(:"===", true),
Spectator::ValueStub.new(:"=~", nil),
Spectator::ValueStub.new(:class, EmptyDouble),
Spectator::ValueStub.new(:dup, EmptyDouble.new),
Spectator::ValueStub.new(:"in?", true),
Spectator::ValueStub.new(:inspect, "inspect"),
Spectator::ValueStub.new(:itself, EmptyDouble.new),
Spectator::ValueStub.new(:"not_nil!", EmptyDouble.new),
Spectator::ValueStub.new(:pretty_inspect, "pretty_inspect"),
Spectator::ValueStub.new(:tap, EmptyDouble.new),
Spectator::ValueStub.new(:to_json, "to_json"),
Spectator::ValueStub.new(:to_pretty_json, "to_pretty_json"),
Spectator::ValueStub.new(:to_s, "to_s"),
Spectator::ValueStub.new(:to_yaml, "to_yaml"),
Spectator::ValueStub.new(:try, nil),
Spectator::ValueStub.new(:object_id, 42_u64),
Spectator::ValueStub.new(:"same?", true),
] of Spectator::Stub)
end
it "responds with defined messages" do
aggregate_failures do
expect(dbl.!=(42)).to be_false
expect(dbl.!~(42)).to be_false
expect(dbl.==(42)).to be_true
expect(dbl.===(42)).to be_true
expect(dbl.=~(42)).to be_nil
expect(dbl.class).to eq(EmptyDouble)
expect(dbl.dup).to be_a(EmptyDouble)
expect(dbl.in?([42])).to eq(true)
expect(dbl.in?(1, 2, 3)).to eq(true)
expect(dbl.inspect).to eq("inspect")
expect(dbl.itself).to be_a(EmptyDouble)
expect(dbl.not_nil!).to be_a(EmptyDouble)
expect(dbl.pretty_inspect).to eq("pretty_inspect")
expect(dbl.tap { nil }).to be_a(EmptyDouble)
expect(dbl.to_json).to eq("to_json")
expect(dbl.to_pretty_json).to eq("to_pretty_json")
expect(dbl.to_s).to eq("to_s")
expect(dbl.to_yaml).to eq("to_yaml")
expect(dbl.try { nil }).to be_nil
expect(dbl.object_id).to eq(42_u64)
expect(dbl.same?(dbl)).to be_true
expect(dbl.same?(nil)).to be_true
end
end
it "has a non-union return type" do
expect(dbl.inspect).to compile_as(String)
end
end
context "without common object methods" do
subject(dbl) { EmptyDouble.new }
it "returns original implementation with undefined messages" do
io = IO::Memory.new
pp = PrettyPrint.new(io)
hasher = Crystal::Hasher.new
aggregate_failures do
expect(dbl.!=(42)).to be_true
expect(dbl.!~(42)).to be_true
expect(dbl.==(42)).to be_false
expect(dbl.===(42)).to be_false
expect(dbl.=~(42)).to be_nil
expect(dbl.class).to eq(EmptyDouble)
expect(dbl.dup).to be_a(EmptyDouble)
expect(dbl.hash(hasher)).to be_a(Crystal::Hasher)
expect(dbl.hash).to be_a(UInt64)
expect(dbl.in?([42])).to be_false
expect(dbl.in?(1, 2, 3)).to be_false
expect(dbl.itself).to be(dbl)
expect(dbl.not_nil!).to be(dbl)
expect(dbl.pretty_print(pp)).to be_nil
expect(dbl.tap { nil }).to be(dbl)
expect(dbl.try { nil }).to be_nil
expect(dbl.object_id).to be_a(UInt64)
expect(dbl.same?(dbl)).to be_true
expect(dbl.same?(nil)).to be_false
end
end
it "reports arguments when they don't match" do
expect { dbl.same?(123, :xyz) }.to raise_error(Spectator::UnexpectedMessage, /\(123, :xyz\)/)
end
end
context "with arguments constraints" do
let(arguments) { Spectator::Arguments.capture(/foo/) }
context "without common object methods" do
Spectator::Double.define(TestDouble) do
abstract_stub abstract def foo(value) : String
abstract_stub abstract def foo(value, & : -> _) : String
end
let(stub) { Spectator::ValueStub.new(:foo, "bar", arguments).as(Spectator::Stub) }
subject(dbl) { TestDouble.new([stub]) }
it "returns the response when constraint satisfied" do
expect(dbl.foo("foobar")).to eq("bar")
end
it "raises an error when constraint unsatisfied" do
expect { dbl.foo("baz") }.to raise_error(Spectator::UnexpectedMessage)
end
it "raises an error when argument count doesn't match" do
expect { dbl.foo }.to raise_error(Spectator::UnexpectedMessage)
end
it "has a non-union return type" do
expect(dbl.foo("foobar")).to compile_as(String)
end
it "ignores the block argument if not in the constraint" do
expect(dbl.foo("foobar") { nil }).to eq("bar")
end
end
context "with common object methods" do
Spectator::Double.define(TestDouble) do
stub abstract def same?(other : Reference) : Bool
end
let(stub) { Spectator::ValueStub.new(:"same?", true, arguments).as(Spectator::Stub) }
subject(dbl) { TestDouble.new([stub]) }
it "returns the response when constraint satisfied" do
expect(dbl.same?("foobar")).to eq(true)
end
it "raises an error when constraint unsatisfied" do
expect { dbl.same?("baz") }.to raise_error(Spectator::UnexpectedMessage)
end
it "raises an error when argument count doesn't match" do
expect { dbl.same? }.to raise_error(Spectator::UnexpectedMessage)
end
it "has a non-union return type" do
expect(dbl.same?("foobar")).to compile_as(Bool)
end
end
end
context "class method stubs" do
Spectator::Double.define(ClassDouble) do
stub def self.foo
:stub
end
stub def self.bar(arg)
arg
end
stub def self.baz(arg, &)
yield
end
end
subject(dbl) { ClassDouble }
let(foo_stub) { Spectator::ValueStub.new(:foo, :override) }
after { dbl._spectator_clear_stubs }
it "overrides an existing method" do
expect { dbl._spectator_define_stub(foo_stub) }.to change { dbl.foo }.from(:stub).to(:override)
end
it "doesn't affect other methods" do
expect { dbl._spectator_define_stub(foo_stub) }.to_not change { dbl.bar(42) }
end
it "replaces an existing stub" do
dbl._spectator_define_stub(foo_stub)
stub = Spectator::ValueStub.new(:foo, :replacement)
expect { dbl._spectator_define_stub(stub) }.to change { dbl.foo }.to(:replacement)
end
it "picks the correct stub based on arguments" do
stub1 = Spectator::ValueStub.new(:bar, :fallback)
stub2 = Spectator::ValueStub.new(:bar, :override, Spectator::Arguments.capture(:match))
dbl._spectator_define_stub(stub1)
dbl._spectator_define_stub(stub2)
aggregate_failures do
expect(dbl.bar(:wrong)).to eq(:fallback)
expect(dbl.bar(:match)).to eq(:override)
end
end
it "only uses a stub if an argument constraint is met" do
stub = Spectator::ValueStub.new(:bar, :override, Spectator::Arguments.capture(:match))
dbl._spectator_define_stub(stub)
aggregate_failures do
expect(dbl.bar(:original)).to eq(:original)
expect(dbl.bar(:match)).to eq(:override)
end
end
it "ignores the block argument if not in the constraint" do
stub1 = Spectator::ValueStub.new(:baz, 1)
stub2 = Spectator::ValueStub.new(:baz, 2, Spectator::Arguments.capture(3))
dbl._spectator_define_stub(stub1)
dbl._spectator_define_stub(stub2)
aggregate_failures do
expect(dbl.baz(5) { 42 }).to eq(1)
expect(dbl.baz(3) { 42 }).to eq(2)
end
end
describe "._spectator_clear_stubs" do
before { dbl._spectator_define_stub(foo_stub) }
it "removes previously defined stubs" do
expect { dbl._spectator_clear_stubs }.to change { dbl.foo }.from(:override).to(:stub)
end
end
describe "._spectator_calls" do
before { dbl._spectator_clear_calls }
# Retrieves symbolic names of methods called on a double.
def called_method_names(dbl)
dbl._spectator_calls.map(&.method)
end
it "stores calls to stubbed methods" do
expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[]).to(%i[foo])
end
it "stores multiple calls to the same stub" do
dbl.foo
expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[foo]).to(%i[foo foo])
end
it "stores arguments for a call" do
dbl.bar(42)
args = Spectator::Arguments.capture(42)
call = dbl._spectator_calls.first
expect(call.arguments).to eq(args)
end
end
end
describe "#_spectator_define_stub" do
subject(dbl) { FooBarDouble.new }
let(stub3) { Spectator::ValueStub.new(:foo, 3) }
let(stub5) { Spectator::ValueStub.new(:foo, 5) }
let(stub7) { Spectator::ValueStub.new(:foo, 7, Spectator::Arguments.capture(:lucky)) }
it "overrides an existing method" do
expect { dbl._spectator_define_stub(stub3) }.to change { dbl.foo }.from(42).to(3)
end
it "replaces an existing stub" do
dbl._spectator_define_stub(stub3)
expect { dbl._spectator_define_stub(stub5) }.to change { dbl.foo }.from(3).to(5)
end
it "doesn't affect other methods" do
expect { dbl._spectator_define_stub(stub5) }.to_not change { dbl.bar }
end
it "picks the correct stub based on arguments" do
dbl._spectator_define_stub(stub5)
dbl._spectator_define_stub(stub7)
aggregate_failures do
expect(dbl.foo).to eq(5)
expect(dbl.foo(:lucky)).to eq(7)
end
end
it "only uses a stub if an argument constraint is met" do
dbl._spectator_define_stub(stub7)
aggregate_failures do
expect(dbl.foo).to eq(42)
expect(dbl.foo(:lucky)).to eq(7)
end
end
it "ignores the block argument if not in the constraint" do
dbl._spectator_define_stub(stub5)
dbl._spectator_define_stub(stub7)
aggregate_failures do
expect(dbl.foo { nil }).to eq(5)
expect(dbl.foo(:lucky) { nil }).to eq(7)
end
end
end
describe "#_spectator_clear_stubs" do
subject(dbl) { FooBarDouble.new }
let(stub) { Spectator::ValueStub.new(:foo, 5) }
before { dbl._spectator_define_stub(stub) }
it "removes previously defined stubs" do
expect { dbl._spectator_clear_stubs }.to change { dbl.foo }.from(5).to(42)
end
end
describe "#_spectator_calls" do
subject(dbl) { FooBarDouble.new }
let(stub) { Spectator::ValueStub.new(:foo, 5) }
before { dbl._spectator_define_stub(stub) }
# Retrieves symbolic names of methods called on a double.
def called_method_names(dbl)
dbl._spectator_calls.map(&.method)
end
it "stores calls to stubbed methods" do
expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[]).to(%i[foo])
end
it "stores multiple calls to the same stub" do
dbl.foo
expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[foo]).to(%i[foo foo])
end
it "stores calls to non-stubbed methods" do
expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage, /baz/)
expect(called_method_names(dbl)).to contain(:baz)
end
it "stores arguments for a call" do
dbl.foo(42)
args = Spectator::Arguments.capture(42)
call = dbl._spectator_calls.first
expect(call.arguments).to eq(args)
end
end
describe "#to_s" do
subject(string) { dbl.to_s }
context "with a name" do
let(dbl) { FooBarDouble.new }
it "indicates it's a double" do
expect(string).to contain("Double")
end
it "contains the double name" do
expect(string).to contain("dbl-name")
end
end
context "without a name" do
let(dbl) { EmptyDouble.new }
it "indicates it's a double" do
expect(string).to contain("Double")
end
it "contains \"Anonymous\"" do
expect(string).to contain("Anonymous")
end
end
end
describe "#inspect" do
subject(string) { dbl.inspect }
context "with a name" do
let(dbl) { FooBarDouble.new }
it "indicates it's a double" do
expect(string).to contain("Double")
end
it "contains the double name" do
expect(string).to contain("dbl-name")
end
it "contains the object ID" do
expect(string).to contain(dbl.object_id.to_s(16))
end
end
context "without a name" do
let(dbl) { EmptyDouble.new }
it "indicates it's a double" do
expect(string).to contain("Double")
end
it "contains \"Anonymous\"" do
expect(string).to contain("Anonymous")
end
it "contains the object ID" do
expect(string).to contain(dbl.object_id.to_s(16))
end
end
end
end

View file

@ -1,166 +0,0 @@
require "../../spec_helper"
Spectator.describe Spectator::ExceptionStub do
let(method_call) { Spectator::MethodCall.capture(:foo) }
let(location) { Spectator::Location.new(__FILE__, __LINE__) }
let(exception) { RuntimeError.new("Test exception") }
subject(stub) { described_class.new(:foo, exception, location: location) }
it "stores the method name" do
expect(stub.method).to eq(:foo)
end
it "stores the location" do
expect(stub.location).to eq(location)
end
it "raises the specified exception" do
expect { stub.call(method_call) }.to raise_error(RuntimeError, "Test exception")
end
context Spectator::StubModifiers do
describe "#and_return(value)" do
let(arguments) { Spectator::Arguments.capture(/foo/) }
let(location) { Spectator::Location.new(__FILE__, __LINE__) }
let(original) { Spectator::ExceptionStub.new(:foo, exception, arguments, location) }
subject(stub) { original.and_return(123) }
it "produces a stub that returns a value" do
expect(stub.call(method_call)).to eq(123)
end
it "retains the method name" do
expect(stub.method).to eq(:foo)
end
it "retains the arguments constraint" do
expect(stub.constraint).to eq(arguments)
end
it "retains the location" do
expect(stub.location).to eq(location)
end
end
describe "#and_return(*values)" do
let(arguments) { Spectator::Arguments.capture(/foo/) }
let(location) { Spectator::Location.new(__FILE__, __LINE__) }
let(original) { Spectator::ExceptionStub.new(:foo, exception, arguments, location) }
subject(stub) { original.and_return(3, 2, 1, 0) }
it "produces a stub that returns values" do
values = Array.new(5) { stub.call(method_call) }
expect(values).to eq([3, 2, 1, 0, 0])
end
it "retains the method name" do
expect(stub.method).to eq(:foo)
end
it "retains the arguments constraint" do
expect(stub.constraint).to eq(arguments)
end
it "retains the location" do
expect(stub.location).to eq(location)
end
end
describe "#and_raise" do
let(arguments) { Spectator::Arguments.capture(/foo/) }
let(location) { Spectator::Location.new(__FILE__, __LINE__) }
let(original) { Spectator::ExceptionStub.new(:foo, exception, arguments, location) }
let(new_exception) { ArgumentError.new("Test argument error") }
subject(stub) { original.and_raise(new_exception) }
it "produces a stub that raises" do
expect { stub.call(method_call) }.to raise_error(ArgumentError, "Test argument error")
end
context "with a class and message" do
subject(stub) { original.and_raise(ArgumentError, "Test argument error") }
it "produces a stub that raises" do
expect { stub.call(method_call) }.to raise_error(ArgumentError, "Test argument error")
end
end
context "with a message" do
subject(stub) { original.and_raise("Test exception") }
it "produces a stub that raises" do
expect { stub.call(method_call) }.to raise_error(Exception, "Test exception")
end
end
context "with a class" do
subject(stub) { original.and_raise(ArgumentError) }
it "produces a stub that raises" do
expect { stub.call(method_call) }.to raise_error(ArgumentError)
end
end
it "retains the method name" do
expect(stub.method).to eq(:foo)
end
it "retains the arguments constraint" do
expect(stub.constraint).to eq(arguments)
end
it "retains the location" do
expect(stub.location).to eq(location)
end
end
end
describe "#===" do
subject { stub === call }
context "with a matching method name" do
let(call) { Spectator::MethodCall.capture(:foo, "foobar") }
it "returns true" do
is_expected.to be_true
end
end
context "with a different method name" do
let(call) { Spectator::MethodCall.capture(:bar, "foobar") }
it "returns false" do
is_expected.to be_false
end
end
context "with a constraint" do
let(constraint) { Spectator::Arguments.capture(/foo/) }
let(stub) { Spectator::ValueStub.new(:foo, 42, constraint) }
context "with a matching method name" do
let(call) { Spectator::MethodCall.capture(:foo, "foobar") }
it "returns true" do
is_expected.to be_true
end
context "with a non-matching arguments" do
let(call) { Spectator::MethodCall.capture(:foo, "baz") }
it "returns false" do
is_expected.to be_false
end
end
end
context "with a different method name" do
let(call) { Spectator::MethodCall.capture(:bar, "foobar") }
it "returns false" do
is_expected.to be_false
end
end
end
end
end

View file

@ -1,325 +0,0 @@
require "../../spec_helper"
Spectator.describe Spectator::FormalArguments do
subject(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
it "stores the arguments" do
expect(arguments).to have_attributes(
args: {arg1: 42, arg2: "foo"},
splat_name: :splat,
splat: {:x, :y, :z},
kwargs: {bar: "baz", qux: 123}
)
end
describe ".build" do
subject { Spectator::FormalArguments.build({arg1: 42, arg2: "foo"}, :splat, {1, 2, 3}, {bar: "baz", qux: 123}) }
it "stores the arguments and keyword arguments" do
is_expected.to have_attributes(
args: {arg1: 42, arg2: "foo"},
splat_name: :splat,
splat: {1, 2, 3},
kwargs: {bar: "baz", qux: 123}
)
end
context "without a splat" do
subject { Spectator::FormalArguments.build({arg1: 42, arg2: "foo"}, {bar: "baz", qux: 123}) }
it "stores the arguments and keyword arguments" do
is_expected.to have_attributes(
args: {arg1: 42, arg2: "foo"},
splat: nil,
kwargs: {bar: "baz", qux: 123}
)
end
end
end
describe "#[](index)" do
it "returns a positional argument" do
aggregate_failures do
expect(arguments[0]).to eq(42)
expect(arguments[1]).to eq("foo")
end
end
it "returns splat arguments" do
aggregate_failures do
expect(arguments[2]).to eq(:x)
expect(arguments[3]).to eq(:y)
expect(arguments[4]).to eq(:z)
end
end
context "with named positional arguments" do
subject(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
it "returns a positional argument" do
aggregate_failures do
expect(arguments[0]).to eq(42)
expect(arguments[1]).to eq("foo")
end
end
it "returns splat arguments" do
aggregate_failures do
expect(arguments[2]).to eq(:x)
expect(arguments[3]).to eq(:y)
expect(arguments[4]).to eq(:z)
end
end
end
end
describe "#[](symbol)" do
it "returns a keyword argument" do
aggregate_failures do
expect(arguments[:bar]).to eq("baz")
expect(arguments[:qux]).to eq(123)
end
end
context "with named positional arguments" do
subject(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
it "returns a positional argument" do
aggregate_failures do
expect(arguments[:arg1]).to eq(42)
expect(arguments[:arg2]).to eq("foo")
end
end
it "returns a keyword argument" do
aggregate_failures do
expect(arguments[:bar]).to eq("baz")
expect(arguments[:qux]).to eq(123)
end
end
end
end
describe "#to_s" do
subject { arguments.to_s }
it "formats the arguments" do
is_expected.to eq("(arg1: 42, arg2: \"foo\", *splat: {:x, :y, :z}, bar: \"baz\", qux: 123)")
end
context "when empty" do
let(arguments) { Spectator::FormalArguments.none }
it "returns (no args)" do
is_expected.to eq("(no args)")
end
end
context "with a splat and no arguments" do
let(arguments) { Spectator::FormalArguments.build(NamedTuple.new, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
it "omits the splat name" do
is_expected.to eq("(:x, :y, :z, bar: \"baz\", qux: 123)")
end
end
end
describe "#==" do
subject { arguments == other }
context "with Arguments" do
context "with equal arguments" do
let(other) { Spectator::Arguments.new(arguments.positional, arguments.kwargs) }
it { is_expected.to be_true }
end
context "with different arguments" do
let(other) { Spectator::Arguments.new({123, :foo, "bar"}, {opt: "foobar"}) }
it { is_expected.to be_false }
end
context "with the same kwargs in a different order" do
let(other) { Spectator::Arguments.new(arguments.positional, {qux: 123, bar: "baz"}) }
it { is_expected.to be_true }
end
context "with a missing kwarg" do
let(other) { Spectator::Arguments.new(arguments.positional, {bar: "baz"}) }
it { is_expected.to be_false }
end
context "with an extra kwarg" do
let(other) { Spectator::Arguments.new(arguments.positional, {bar: "baz", qux: 123, extra: 0}) }
it { is_expected.to be_false }
end
end
context "with FormalArguments" do
context "with equal arguments" do
let(other) { arguments }
it { is_expected.to be_true }
end
context "with different arguments" do
let(other) { Spectator::FormalArguments.new({arg1: 123, arg2: :foo, arg3: "bar"}, :splat, {1, 2, 3}, {opt: "foobar"}) }
it { is_expected.to be_false }
end
context "with the same kwargs in a different order" do
let(other) { Spectator::FormalArguments.new(arguments.args, arguments.splat_name, arguments.splat, {qux: 123, bar: "baz"}) }
it { is_expected.to be_true }
end
context "with a missing kwarg" do
let(other) { Spectator::FormalArguments.new(arguments.args, arguments.splat_name, arguments.splat, {bar: "baz"}) }
it { is_expected.to be_false }
end
context "with an extra kwarg" do
let(other) { Spectator::FormalArguments.new(arguments.args, arguments.splat_name, arguments.splat, {bar: "baz", qux: 123, extra: 0}) }
it { is_expected.to be_false }
end
context "with different splat arguments" do
let(other) { Spectator::FormalArguments.new(arguments.args, arguments.splat_name, {1, 2, 3}, arguments.kwargs) }
it { is_expected.to be_false }
end
context "with mixed positional tuple types" do
let(other) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, arguments.splat_name, arguments.splat, arguments.kwargs) }
it { is_expected.to be_true }
end
context "with mixed positional tuple types (flipped)" do
let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
let(other) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
it { is_expected.to be_true }
end
end
end
describe "#===" do
subject { pattern === arguments }
context "with Arguments" do
let(arguments) { Spectator::Arguments.new({42, "foo"}, {bar: "baz", qux: 123}) }
context "with equal arguments" do
let(pattern) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, {bar: "baz", qux: 123}) }
it { is_expected.to be_true }
end
context "with matching arguments" do
let(pattern) { Spectator::FormalArguments.new({arg1: Int32, arg2: /foo/}, {bar: /baz/, qux: Int32}) }
it { is_expected.to be_true }
end
context "with non-matching arguments" do
let(pattern) { Spectator::FormalArguments.new({arg1: Float64, arg2: /bar/}, {bar: /foo/, qux: "123"}) }
it { is_expected.to be_false }
end
context "with different arguments" do
let(pattern) { Spectator::FormalArguments.new({arg1: 123, arg2: :foo, arg3: "bar"}, {opt: "foobar"}) }
it { is_expected.to be_false }
end
context "with the same kwargs in a different order" do
let(pattern) { Spectator::FormalArguments.new({arg1: Int32, arg2: /foo/}, {qux: Int32, bar: /baz/}) }
it { is_expected.to be_true }
end
context "with an additional kwarg" do
let(pattern) { Spectator::FormalArguments.new({arg1: Int32, arg2: /foo/}, {bar: /baz/}) }
it { is_expected.to be_true }
end
context "with a missing kwarg" do
let(pattern) { Spectator::FormalArguments.new({arg1: Int32, arg2: /foo/}, {bar: /baz/, qux: Int32, extra: 0}) }
it { is_expected.to be_false }
end
end
context "with FormalArguments" do
context "with equal arguments" do
let(pattern) { arguments }
it { is_expected.to be_true }
end
context "with matching arguments" do
let(pattern) { Spectator::FormalArguments.new({arg1: Int32, arg2: /foo/}, :splat, {Symbol, Symbol, :z}, {bar: /baz/, qux: Int32}) }
it { is_expected.to be_true }
end
context "with non-matching arguments" do
let(pattern) { Spectator::FormalArguments.new({arg1: Float64, arg2: /bar/}, :splat, {String, Int32, :x}, {bar: /foo/, qux: "123"}) }
it { is_expected.to be_false }
end
context "with different arguments" do
let(pattern) { Spectator::FormalArguments.new({arg1: 123, arg2: :foo, arg3: "bar"}, :splat, {1, 2, 3}, {opt: "foobar"}) }
it { is_expected.to be_false }
end
context "with the same kwargs in a different order" do
let(pattern) { Spectator::FormalArguments.new(arguments.args, arguments.splat_name, arguments.splat, {qux: Int32, bar: /baz/}) }
it { is_expected.to be_true }
end
context "with an additional kwarg" do
let(pattern) { Spectator::FormalArguments.new(arguments.args, arguments.splat_name, arguments.splat, {bar: /baz/}) }
it { is_expected.to be_true }
end
context "with a missing kwarg" do
let(pattern) { Spectator::FormalArguments.new(arguments.args, arguments.splat_name, arguments.splat, {bar: /baz/, qux: Int32, extra: 0}) }
it { is_expected.to be_false }
end
context "with different splat arguments" do
let(pattern) { Spectator::FormalArguments.new(arguments.args, arguments.splat_name, {1, 2, 3}, arguments.kwargs) }
it { is_expected.to be_false }
end
context "with matching mixed positional tuple types" do
let(pattern) { Spectator::FormalArguments.new({arg1: Int32, arg2: /foo/}, arguments.splat_name, arguments.splat, arguments.kwargs) }
it { is_expected.to be_true }
end
context "with non-matching mixed positional tuple types" do
let(pattern) { Spectator::FormalArguments.new({arg1: Float64, arg2: /bar/}, arguments.splat_name, arguments.splat, arguments.kwargs) }
it { is_expected.to be_false }
end
end
end
end

View file

@ -1,352 +0,0 @@
require "../../spec_helper"
Spectator.describe Spectator::LazyDouble do
context "plain double" do
subject(dbl) { Spectator::LazyDouble.new("dbl-name", foo: 42, bar: "baz") }
it "responds to defined messages" do
aggregate_failures do
expect(dbl.foo).to eq(42)
expect(dbl.bar).to eq("baz")
end
end
it "fails on undefined messages" do
expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage, /baz/)
end
it "reports the name in errors" do
expect { dbl.baz }.to raise_error(/"dbl-name"/)
end
it "reports arguments" do
expect { dbl.baz(123, "qux", field: :value) }.to raise_error(Spectator::UnexpectedMessage, /\(123, "qux", field: :value\)/)
end
context "blocks" do
it "supports blocks" do
aggregate_failures do
expect(dbl.foo { nil }).to eq(42)
expect(dbl.bar { nil }).to eq("baz")
end
end
it "fails on undefined messages" do
expect do
dbl.baz { nil }
end.to raise_error(Spectator::UnexpectedMessage, /baz/)
end
end
end
context "without a double name" do
subject(dbl) { Spectator::LazyDouble.new }
it "reports as anonymous" do
expect { dbl.baz }.to raise_error(/anonymous/i)
end
end
context "with nillable values" do
subject(dbl) { Spectator::LazyDouble.new(foo: nil.as(String?), bar: nil) }
it "doesn't raise on nil" do
aggregate_failures do
expect(dbl.foo).to be_nil
expect(dbl.bar).to be_nil
end
end
end
context "with common object methods" do
let(dup) { double(:dup) }
subject(dbl) do
Spectator::LazyDouble.new(nil, [
Spectator::ValueStub.new(:"!=", false),
Spectator::ValueStub.new(:"!~", false),
Spectator::ValueStub.new(:"==", true),
Spectator::ValueStub.new(:"===", true),
Spectator::ValueStub.new(:"=~", nil),
Spectator::ValueStub.new(:dup, dup),
Spectator::ValueStub.new(:hash, 42_u64),
Spectator::ValueStub.new(:"in?", true),
Spectator::ValueStub.new(:inspect, "inspect"),
Spectator::ValueStub.new(:itself, dup),
Spectator::ValueStub.new(:"not_nil!", dup),
Spectator::ValueStub.new(:pretty_inspect, "pretty_inspect"),
Spectator::ValueStub.new(:tap, dup),
Spectator::ValueStub.new(:to_json, "to_json"),
Spectator::ValueStub.new(:to_pretty_json, "to_pretty_json"),
Spectator::ValueStub.new(:to_s, "to_s"),
Spectator::ValueStub.new(:to_yaml, "to_yaml"),
Spectator::ValueStub.new(:try, nil),
Spectator::ValueStub.new(:object_id, 42_u64),
Spectator::ValueStub.new(:"same?", true),
] of Spectator::Stub)
end
it "responds with defined messages" do
aggregate_failures do
expect(dbl.!=(42)).to eq(false)
expect(dbl.!~(42)).to eq(false)
expect(dbl.==(42)).to eq(true)
expect(dbl.===(42)).to eq(true)
expect(dbl.=~(42)).to be_nil
expect(dbl.dup).to be(dup)
expect(dbl.hash).to eq(42_u64)
expect(dbl.in?([42])).to eq(true)
expect(dbl.in?(1, 2, 3)).to eq(true)
expect(dbl.inspect).to eq("inspect")
expect(dbl.itself).to be(dup)
expect(dbl.not_nil!).to be(dup)
expect(dbl.pretty_inspect).to eq("pretty_inspect")
expect(dbl.tap { nil }).to be(dup)
expect(dbl.to_json).to eq("to_json")
expect(dbl.to_pretty_json).to eq("to_pretty_json")
expect(dbl.to_s).to eq("to_s")
expect(dbl.to_yaml).to eq("to_yaml")
expect(dbl.try { nil }).to be_nil
expect(dbl.object_id).to eq(42_u64)
expect(dbl.same?(dbl)).to eq(true)
expect(dbl.same?(nil)).to eq(true)
end
end
it "has a non-union return type" do
expect(dbl.inspect).to compile_as(String)
end
end
context "without common object methods" do
subject(dbl) { Spectator::LazyDouble.new }
it "returns the original value" do
io = IO::Memory.new
aggregate_failures do
expect(dbl.!=(42)).to be_true
expect(dbl.!~(42)).to be_true
expect(dbl.==(42)).to be_false
expect(dbl.===(42)).to be_false
expect(dbl.=~(42)).to be_nil
expect(dbl.class).to be_lt(Spectator::LazyDouble)
expect(dbl.in?([42])).to be_false
expect(dbl.in?(1, 2, 3)).to be_false
expect(dbl.itself).to be(dbl)
expect(dbl.not_nil!).to be(dbl)
expect(dbl.tap { nil }).to be(dbl)
expect(dbl.to_s(io)).to be_nil
expect(dbl.try { nil }).to be_nil
expect(dbl.same?(dbl)).to be_true
expect(dbl.same?(nil)).to be_false
end
end
end
context "with arguments constraints" do
let(arguments) { Spectator::Arguments.capture(/foo/) }
context "without common object methods" do
let(stub) { Spectator::ValueStub.new(:foo, "bar", arguments).as(Spectator::Stub) }
subject(dbl) { Spectator::LazyDouble.new(nil, [stub], foo: "fallback") }
it "returns the response when constraint satisfied" do
expect(dbl.foo("foobar")).to eq("bar")
end
it "returns the fallback value when constraint unsatisfied" do
expect { dbl.foo("baz") }.to eq("fallback")
end
it "returns the fallback value when argument count doesn't match" do
expect { dbl.foo }.to eq("fallback")
end
end
context "with common object methods" do
let(stub) { Spectator::ValueStub.new(:"same?", true, arguments).as(Spectator::Stub) }
subject(dbl) { Spectator::LazyDouble.new(nil, [stub]) }
it "returns the response when constraint satisfied" do
expect(dbl.same?("foobar")).to eq(true)
end
it "raises an error when constraint unsatisfied" do
expect { dbl.same?("baz") }.to raise_error(Spectator::UnexpectedMessage)
end
it "raises an error when argument count doesn't match" do
expect { dbl.same? }.to raise_error(Spectator::UnexpectedMessage)
end
context "with a fallback defined" do
subject(dbl) { Spectator::LazyDouble.new(nil, [stub], "same?": true) }
it "returns the fallback when constraint unsatisfied" do
expect(dbl.same?("baz")).to be_true
end
end
end
end
describe "#_spectator_define_stub" do
subject(dbl) { Spectator::LazyDouble.new(foo: 42, bar: "baz") }
let(stub3) { Spectator::ValueStub.new(:foo, 3) }
let(stub5) { Spectator::ValueStub.new(:foo, 5) }
let(stub7) { Spectator::ValueStub.new(:foo, 7, Spectator::Arguments.capture(:lucky)) }
it "overrides an existing method" do
expect { dbl._spectator_define_stub(stub3) }.to change { dbl.foo }.from(42).to(3)
end
it "replaces an existing stub" do
dbl._spectator_define_stub(stub3)
expect { dbl._spectator_define_stub(stub5) }.to change { dbl.foo }.from(3).to(5)
end
it "doesn't affect other methods" do
expect { dbl._spectator_define_stub(stub5) }.to_not change { dbl.bar }
end
it "picks the correct stub based on arguments" do
dbl._spectator_define_stub(stub5)
dbl._spectator_define_stub(stub7)
aggregate_failures do
expect(dbl.foo).to eq(5)
expect(dbl.foo(:lucky)).to eq(7)
end
end
it "only uses a stub if an argument constraint is met" do
dbl._spectator_define_stub(stub7)
aggregate_failures do
expect(dbl.foo).to eq(42)
expect(dbl.foo(:lucky)).to eq(7)
end
end
it "ignores the block argument if not in the constraint" do
dbl._spectator_define_stub(stub5)
dbl._spectator_define_stub(stub7)
aggregate_failures do
expect(dbl.foo { nil }).to eq(5)
expect(dbl.foo(:lucky) { nil }).to eq(7)
end
end
context "with previously undefined methods" do
it "raises an error" do
stub = Spectator::ValueStub.new(:baz, :xyz)
expect { dbl._spectator_define_stub(stub) }.to raise_error(/stub/)
end
end
end
describe "#_spectator_clear_stubs" do
subject(dbl) { Spectator::LazyDouble.new(foo: 42, bar: "baz") }
let(stub) { Spectator::ValueStub.new(:foo, 5) }
before { dbl._spectator_define_stub(stub) }
it "removes previously defined stubs" do
expect { dbl._spectator_clear_stubs }.to change { dbl.foo }.from(5).to(42)
end
end
describe "#_spectator_calls" do
subject(dbl) { Spectator::LazyDouble.new(foo: 42, bar: "baz") }
let(stub) { Spectator::ValueStub.new(:foo, 5) }
before { dbl._spectator_define_stub(stub) }
# Retrieves symbolic names of methods called on a double.
def called_method_names(dbl)
dbl._spectator_calls.map(&.method)
end
it "stores calls to stubbed methods" do
expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[]).to(%i[foo])
end
it "stores multiple calls to the same stub" do
dbl.foo
expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[foo]).to(%i[foo foo])
end
it "stores calls to non-stubbed methods" do
expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage, /baz/)
expect(called_method_names(dbl)).to contain(:baz)
end
it "stores arguments for a call" do
dbl.foo(42)
args = Spectator::Arguments.capture(42)
call = dbl._spectator_calls.first
expect(call.arguments).to eq(args)
end
end
describe "#to_s" do
subject(string) { dbl.to_s }
context "with a name" do
let(dbl) { Spectator::LazyDouble.new("dbl-name") }
it "indicates it's a double" do
expect(string).to contain("LazyDouble")
end
it "contains the double name" do
expect(string).to contain("dbl-name")
end
end
context "without a name" do
let(dbl) { Spectator::LazyDouble.new }
it "contains the double type" do
expect(string).to contain("LazyDouble")
end
it "contains \"Anonymous\"" do
expect(string).to contain("Anonymous")
end
end
end
describe "#inspect" do
subject(string) { dbl.inspect }
context "with a name" do
let(dbl) { Spectator::LazyDouble.new("dbl-name") }
it "contains the double type" do
expect(string).to contain("LazyDouble")
end
it "contains the double name" do
expect(string).to contain("dbl-name")
end
it "contains the object ID" do
expect(string).to contain(dbl.object_id.to_s(16))
end
end
context "without a name" do
let(dbl) { Spectator::LazyDouble.new }
it "contains the double type" do
expect(string).to contain("LazyDouble")
end
it "contains \"Anonymous\"" do
expect(string).to contain("Anonymous")
end
it "contains the object ID" do
expect(string).to contain(dbl.object_id.to_s(16))
end
end
end
end

View file

@ -1,26 +0,0 @@
require "../../spec_helper"
Spectator.describe Spectator::MethodCall do
let(arguments) { Spectator::Arguments.capture(42, "foobar", foo: :bar) }
subject(call) { Spectator::MethodCall.new(:foo, arguments) }
it "stores the method name" do
expect(&.method).to eq(:foo)
end
it "stores arguments" do
expect(&.arguments).to eq(arguments)
end
describe ".capture" do
subject(call) { Spectator::MethodCall.capture(:foo, 42, "foobar", foo: :bar) }
it "stores the method name" do
expect(&.method).to eq(:foo)
end
it "stores arguments" do
expect(&.arguments).to eq(arguments)
end
end
end

File diff suppressed because it is too large Load diff

View file

@ -1,173 +0,0 @@
require "../../spec_helper"
Spectator.describe Spectator::MultiValueStub do
let(method_call) { Spectator::MethodCall.capture(:foo) }
let(location) { Spectator::Location.new(__FILE__, __LINE__) }
subject(stub) { described_class.new(:foo, [3, 5, 7], location: location) }
it "stores the method name" do
expect(stub.method).to eq(:foo)
end
it "stores the location" do
expect(stub.location).to eq(location)
end
describe "#call" do
it "returns the values in order" do
values = Array.new(3) { stub.call(method_call) }
expect(values).to eq([3, 5, 7])
end
it "returns the final value after exhausting other values" do
values = Array.new(5) { stub.call(method_call) }
expect(values).to eq([3, 5, 7, 7, 7])
end
end
context Spectator::StubModifiers do
describe "#and_return(value)" do
let(arguments) { Spectator::Arguments.capture(/foo/) }
let(location) { Spectator::Location.new(__FILE__, __LINE__) }
let(original) { Spectator::MultiValueStub.new(:foo, [3, 5, 7], arguments, location) }
subject(stub) { original.and_return(123) }
it "produces a stub that returns a value" do
expect(stub.call(method_call)).to eq(123)
end
it "retains the method name" do
expect(stub.method).to eq(:foo)
end
it "retains the arguments constraint" do
expect(stub.constraint).to eq(arguments)
end
it "retains the location" do
expect(stub.location).to eq(location)
end
end
describe "#and_return(*values)" do
let(arguments) { Spectator::Arguments.capture(/foo/) }
let(location) { Spectator::Location.new(__FILE__, __LINE__) }
let(original) { Spectator::MultiValueStub.new(:foo, [3, 5, 7], arguments, location) }
subject(stub) { original.and_return(3, 2, 1, 0) }
it "produces a stub that returns values" do
values = Array.new(5) { stub.call(method_call) }
expect(values).to eq([3, 2, 1, 0, 0])
end
it "retains the method name" do
expect(stub.method).to eq(:foo)
end
it "retains the arguments constraint" do
expect(stub.constraint).to eq(arguments)
end
it "retains the location" do
expect(stub.location).to eq(location)
end
end
describe "#and_raise" do
let(arguments) { Spectator::Arguments.capture(/foo/) }
let(location) { Spectator::Location.new(__FILE__, __LINE__) }
let(original) { Spectator::MultiValueStub.new(:foo, [3, 5, 7], arguments, location) }
let(new_exception) { ArgumentError.new("Test argument error") }
subject(stub) { original.and_raise(new_exception) }
it "produces a stub that raises" do
expect { stub.call(method_call) }.to raise_error(ArgumentError, "Test argument error")
end
context "with a class and message" do
subject(stub) { original.and_raise(ArgumentError, "Test argument error") }
it "produces a stub that raises" do
expect { stub.call(method_call) }.to raise_error(ArgumentError, "Test argument error")
end
end
context "with a message" do
subject(stub) { original.and_raise("Test exception") }
it "produces a stub that raises" do
expect { stub.call(method_call) }.to raise_error(Exception, "Test exception")
end
end
context "with a class" do
subject(stub) { original.and_raise(ArgumentError) }
it "produces a stub that raises" do
expect { stub.call(method_call) }.to raise_error(ArgumentError)
end
end
it "retains the method name" do
expect(stub.method).to eq(:foo)
end
it "retains the arguments constraint" do
expect(stub.constraint).to eq(arguments)
end
it "retains the location" do
expect(stub.location).to eq(location)
end
end
end
describe "#===" do
subject { stub === call }
context "with a matching method name" do
let(call) { Spectator::MethodCall.capture(:foo, "foobar") }
it "returns true" do
is_expected.to be_true
end
end
context "with a different method name" do
let(call) { Spectator::MethodCall.capture(:bar, "foobar") }
it "returns false" do
is_expected.to be_false
end
end
context "with a constraint" do
let(constraint) { Spectator::Arguments.capture(/foo/) }
let(stub) { Spectator::ValueStub.new(:foo, 42, constraint) }
context "with a matching method name" do
let(call) { Spectator::MethodCall.capture(:foo, "foobar") }
it "returns true" do
is_expected.to be_true
end
context "with a non-matching arguments" do
let(call) { Spectator::MethodCall.capture(:foo, "baz") }
it "returns false" do
is_expected.to be_false
end
end
end
context "with a different method name" do
let(call) { Spectator::MethodCall.capture(:bar, "foobar") }
it "returns false" do
is_expected.to be_false
end
end
end
end
end

View file

@ -1,503 +0,0 @@
require "../../spec_helper"
Spectator.describe Spectator::NullDouble do
Spectator::NullDouble.define(EmptyDouble)
Spectator::NullDouble.define(FooBarDouble, "dbl-name", foo: 42, bar: "baz")
# The subject `dbl` must be carefully used in sub-contexts, otherwise it pollutes parent scopes.
# This changes the type of `dbl` to `Double`, which produces a union of methods and their return types.
context "plain double" do
subject(dbl) { FooBarDouble.new }
it "responds to defined messages" do
aggregate_failures do
expect(dbl.foo).to eq(42)
expect(dbl.bar).to eq("baz")
end
end
it "returns self on undefined messages" do
expect(dbl.baz).to be(dbl)
end
it "has a non-union return type" do
aggregate_failures do
expect(dbl.foo).to compile_as(Int32)
expect(dbl.bar).to compile_as(String)
end
end
context "blocks" do
it "supports blocks" do
aggregate_failures do
expect(dbl.foo { nil }).to eq(42)
expect(dbl.bar { nil }).to eq("baz")
end
end
it "supports blocks and has non-union return types" do
aggregate_failures do
expect(dbl.foo { nil }).to compile_as(Int32)
expect(dbl.bar { nil }).to compile_as(String)
end
end
it "returns self on undefined messages" do
expect(dbl.baz { nil }).to be(dbl)
end
end
end
context "with abstract stubs and return type annotations" do
Spectator::NullDouble.define(TestDouble2) do
abstract_stub abstract def foo(value) : String
end
let(arguments) { Spectator::Arguments.capture(/foo/) }
let(stub) { Spectator::ValueStub.new(:foo, "bar", arguments).as(Spectator::Stub) }
subject(dbl) { TestDouble2.new([stub]) }
it "enforces the return type" do
expect(dbl.foo("foobar")).to compile_as(String)
end
it "raises on non-matching arguments" do
expect { dbl.foo("bar") }.to raise_error(Spectator::UnexpectedMessage, /foo/)
end
it "raises on non-matching stub" do
stub = Spectator::ValueStub.new(:foo, 42, arguments).as(Spectator::Stub)
dbl._spectator_define_stub(stub)
expect { dbl.foo("foobar") }.to raise_error(TypeCastError, /String/)
end
end
context "with nillable return type annotations" do
Spectator::NullDouble.define(TestDouble) do
abstract_stub abstract def foo : String?
abstract_stub abstract def bar : Nil
end
let(foo_stub) { Spectator::ValueStub.new(:foo, nil).as(Spectator::Stub) }
let(bar_stub) { Spectator::ValueStub.new(:bar, nil).as(Spectator::Stub) }
subject(dbl) { TestDouble.new([foo_stub, bar_stub]) }
it "doesn't raise on nil" do
aggregate_failures do
expect(dbl.foo).to be_nil
expect(dbl.bar).to be_nil
end
end
end
context "with a method that uses NoReturn" do
Spectator::NullDouble.define(NoReturnDouble) do
abstract_stub abstract def oops : NoReturn
end
subject(dbl) { NoReturnDouble.new }
it "raises a TypeCastError when using a value-based stub" do
stub = Spectator::ValueStub.new(:oops, nil).as(Spectator::Stub)
dbl._spectator_define_stub(stub)
expect { dbl.oops }.to raise_error(TypeCastError, /NoReturn/)
end
it "raises when using an exception stub" do
exception = ArgumentError.new("bogus")
stub = Spectator::ExceptionStub.new(:oops, exception).as(Spectator::Stub)
dbl._spectator_define_stub(stub)
expect { dbl.oops }.to raise_error(ArgumentError, "bogus")
end
end
context "with common object methods" do
subject(dbl) do
EmptyDouble.new([
Spectator::ValueStub.new(:"!=", false),
Spectator::ValueStub.new(:"!~", false),
Spectator::ValueStub.new(:"==", true),
Spectator::ValueStub.new(:"===", true),
Spectator::ValueStub.new(:"=~", nil),
Spectator::ValueStub.new(:class, EmptyDouble),
Spectator::ValueStub.new(:dup, EmptyDouble.new),
Spectator::ValueStub.new(:"in?", true),
Spectator::ValueStub.new(:inspect, "inspect"),
Spectator::ValueStub.new(:itself, EmptyDouble.new),
Spectator::ValueStub.new(:"not_nil!", EmptyDouble.new),
Spectator::ValueStub.new(:pretty_inspect, "pretty_inspect"),
Spectator::ValueStub.new(:tap, EmptyDouble.new),
Spectator::ValueStub.new(:to_json, "to_json"),
Spectator::ValueStub.new(:to_pretty_json, "to_pretty_json"),
Spectator::ValueStub.new(:to_s, "to_s"),
Spectator::ValueStub.new(:to_yaml, "to_yaml"),
Spectator::ValueStub.new(:try, nil),
Spectator::ValueStub.new(:object_id, 42_u64),
Spectator::ValueStub.new(:"same?", true),
] of Spectator::Stub)
end
it "responds with defined messages" do
aggregate_failures do
expect(dbl.!=(42)).to be_false
expect(dbl.!~(42)).to be_false
expect(dbl.==(42)).to be_true
expect(dbl.===(42)).to be_true
expect(dbl.=~(42)).to be_nil
expect(dbl.class).to eq(EmptyDouble)
expect(dbl.dup).to be_a(EmptyDouble)
expect(dbl.in?([42])).to eq(true)
expect(dbl.in?(1, 2, 3)).to eq(true)
expect(dbl.inspect).to eq("inspect")
expect(dbl.itself).to be_a(EmptyDouble)
expect(dbl.not_nil!).to be_a(EmptyDouble)
expect(dbl.pretty_inspect).to eq("pretty_inspect")
expect(dbl.tap { nil }).to be_a(EmptyDouble)
expect(dbl.to_json).to eq("to_json")
expect(dbl.to_pretty_json).to eq("to_pretty_json")
expect(dbl.to_s).to eq("to_s")
expect(dbl.to_yaml).to eq("to_yaml")
expect(dbl.try { nil }).to be_nil
expect(dbl.object_id).to eq(42_u64)
expect(dbl.same?(dbl)).to be_true
expect(dbl.same?(nil)).to be_true
end
end
it "has a non-union return type" do
expect(dbl.inspect).to compile_as(String)
end
end
context "without common object methods" do
subject(dbl) { EmptyDouble.new }
it "returns original implementation with undefined messages" do
hasher = Crystal::Hasher.new
aggregate_failures do
expect(dbl.!=(42)).to be_true
expect(dbl.!~(42)).to be_true
expect(dbl.==(42)).to be_false
expect(dbl.===(42)).to be_false
expect(dbl.=~(42)).to be_nil
expect(dbl.class).to eq(EmptyDouble)
expect(dbl.dup).to be_a(EmptyDouble)
expect(dbl.hash(hasher)).to be_a(Crystal::Hasher)
expect(dbl.hash).to be_a(UInt64)
expect(dbl.in?([42])).to be_false
expect(dbl.in?(1, 2, 3)).to be_false
expect(dbl.itself).to be(dbl)
expect(dbl.not_nil!).to be(dbl)
expect(dbl.tap { nil }).to be(dbl)
expect(dbl.try { nil }).to be_nil
expect(dbl.object_id).to be_a(UInt64)
expect(dbl.same?(dbl)).to be_true
expect(dbl.same?(nil)).to be_false
end
end
end
context "with arguments constraints" do
let(arguments) { Spectator::Arguments.capture(/foo/) }
context "without common object methods" do
Spectator::NullDouble.define(TestDouble) do
abstract_stub abstract def foo(value) : String
abstract_stub abstract def foo(value, & : -> _) : String
end
let(stub) { Spectator::ValueStub.new(:foo, "bar", arguments).as(Spectator::Stub) }
subject(dbl) { TestDouble.new([stub]) }
it "returns the response when constraint satisfied" do
expect(dbl.foo("foobar")).to eq("bar")
end
it "raises when constraint unsatisfied" do
expect { dbl.foo("baz") }.to raise_error(Spectator::UnexpectedMessage, /foo/)
end
it "returns self when argument count doesn't match" do
expect(dbl.foo).to be(dbl)
end
it "ignores the block argument if not in the constraint" do
expect(dbl.foo("foobar") { nil }).to eq("bar")
end
end
context "with common object methods" do
Spectator::NullDouble.define(TestDouble) do
stub abstract def hash(hasher) : Crystal::Hasher
end
let(hasher) { Crystal::Hasher.new }
let(stub) { Spectator::ValueStub.new(:hash, hasher, arguments).as(Spectator::Stub) }
subject(dbl) { TestDouble.new([stub]) }
it "returns the response when constraint satisfied" do
expect(dbl.hash("foobar")).to be(hasher)
end
it "raises when constraint unsatisfied" do
expect { dbl.hash("baz") }.to raise_error(Spectator::UnexpectedMessage, /hash/)
end
it "raises when argument count doesn't match" do
expect { dbl.hash }.to raise_error(Spectator::UnexpectedMessage, /hash/)
end
end
end
context "class method stubs" do
Spectator::NullDouble.define(ClassDouble) do
stub def self.foo
:stub
end
stub def self.bar(arg)
arg
end
stub def self.baz(arg, &)
yield
end
end
subject(dbl) { ClassDouble }
let(foo_stub) { Spectator::ValueStub.new(:foo, :override) }
after { dbl._spectator_clear_stubs }
it "overrides an existing method" do
expect { dbl._spectator_define_stub(foo_stub) }.to change { dbl.foo }.from(:stub).to(:override)
end
it "doesn't affect other methods" do
expect { dbl._spectator_define_stub(foo_stub) }.to_not change { dbl.bar(42) }
end
it "replaces an existing stub" do
dbl._spectator_define_stub(foo_stub)
stub = Spectator::ValueStub.new(:foo, :replacement)
expect { dbl._spectator_define_stub(stub) }.to change { dbl.foo }.to(:replacement)
end
it "picks the correct stub based on arguments" do
stub1 = Spectator::ValueStub.new(:bar, :fallback)
stub2 = Spectator::ValueStub.new(:bar, :override, Spectator::Arguments.capture(:match))
dbl._spectator_define_stub(stub1)
dbl._spectator_define_stub(stub2)
aggregate_failures do
expect(dbl.bar(:wrong)).to eq(:fallback)
expect(dbl.bar(:match)).to eq(:override)
end
end
it "only uses a stub if an argument constraint is met" do
stub = Spectator::ValueStub.new(:bar, :override, Spectator::Arguments.capture(:match))
dbl._spectator_define_stub(stub)
aggregate_failures do
expect(dbl.bar(:original)).to eq(:original)
expect(dbl.bar(:match)).to eq(:override)
end
end
it "ignores the block argument if not in the constraint" do
stub1 = Spectator::ValueStub.new(:baz, 1)
stub2 = Spectator::ValueStub.new(:baz, 2, Spectator::Arguments.capture(3))
dbl._spectator_define_stub(stub1)
dbl._spectator_define_stub(stub2)
aggregate_failures do
expect(dbl.baz(5) { 42 }).to eq(1)
expect(dbl.baz(3) { 42 }).to eq(2)
end
end
describe "._spectator_clear_stubs" do
before { dbl._spectator_define_stub(foo_stub) }
it "removes previously defined stubs" do
expect { dbl._spectator_clear_stubs }.to change { dbl.foo }.from(:override).to(:stub)
end
end
describe "._spectator_calls" do
before { dbl._spectator_clear_calls }
# Retrieves symbolic names of methods called on a double.
def called_method_names(dbl)
dbl._spectator_calls.map(&.method)
end
it "stores calls to stubbed methods" do
expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[]).to(%i[foo])
end
it "stores multiple calls to the same stub" do
dbl.foo
expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[foo]).to(%i[foo foo])
end
it "stores arguments for a call" do
dbl.bar(42)
args = Spectator::Arguments.capture(42)
call = dbl._spectator_calls.first
expect(call.arguments).to eq(args)
end
end
end
describe "#_spectator_define_stub" do
subject(dbl) { FooBarDouble.new }
let(stub3) { Spectator::ValueStub.new(:foo, 3) }
let(stub5) { Spectator::ValueStub.new(:foo, 5) }
let(stub7) { Spectator::ValueStub.new(:foo, 7, Spectator::Arguments.capture(:lucky)) }
it "overrides an existing method" do
expect { dbl._spectator_define_stub(stub3) }.to change { dbl.foo }.from(42).to(3)
end
it "replaces an existing stub" do
dbl._spectator_define_stub(stub3)
expect { dbl._spectator_define_stub(stub5) }.to change { dbl.foo }.from(3).to(5)
end
it "doesn't affect other methods" do
expect { dbl._spectator_define_stub(stub5) }.to_not change { dbl.bar }
end
it "picks the correct stub based on arguments" do
dbl._spectator_define_stub(stub5)
dbl._spectator_define_stub(stub7)
aggregate_failures do
expect(dbl.foo).to eq(5)
expect(dbl.foo(:lucky)).to eq(7)
end
end
it "only uses a stub if an argument constraint is met" do
dbl._spectator_define_stub(stub7)
aggregate_failures do
expect(dbl.foo).to eq(42)
expect(dbl.foo(:lucky)).to eq(7)
end
end
it "ignores the block argument if not in the constraint" do
dbl._spectator_define_stub(stub5)
dbl._spectator_define_stub(stub7)
aggregate_failures do
expect(dbl.foo { nil }).to eq(5)
expect(dbl.foo(:lucky) { nil }).to eq(7)
end
end
end
describe "#_spectator_clear_stubs" do
subject(dbl) { FooBarDouble.new }
let(stub) { Spectator::ValueStub.new(:foo, 5) }
before { dbl._spectator_define_stub(stub) }
it "removes previously defined stubs" do
expect { dbl._spectator_clear_stubs }.to change { dbl.foo }.from(5).to(42)
end
end
describe "#_spectator_calls" do
subject(dbl) { FooBarDouble.new }
let(stub) { Spectator::ValueStub.new(:foo, 5) }
before { dbl._spectator_define_stub(stub) }
# Retrieves symbolic names of methods called on a double.
def called_method_names(dbl)
dbl._spectator_calls.map(&.method)
end
it "stores calls to stubbed methods" do
expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[]).to(%i[foo])
end
it "stores multiple calls to the same stub" do
dbl.foo
expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[foo]).to(%i[foo foo])
end
it "stores calls to non-stubbed methods" do
expect { dbl.baz }.to change { called_method_names(dbl) }.from(%i[]).to(%i[baz])
end
it "stores arguments for a call" do
dbl.foo(42)
args = Spectator::Arguments.capture(42)
call = dbl._spectator_calls.first
expect(call.arguments).to eq(args)
end
end
describe "#to_s" do
subject(string) { dbl.to_s }
context "with a name" do
let(dbl) { FooBarDouble.new }
it "indicates it's a double" do
expect(string).to contain("NullDouble")
end
it "contains the double name" do
expect(string).to contain("dbl-name")
end
end
context "without a name" do
let(dbl) { EmptyDouble.new }
it "contains the double type" do
expect(string).to contain("NullDouble")
end
it "contains \"Anonymous\"" do
expect(string).to contain("Anonymous")
end
end
end
describe "#inspect" do
subject(string) { dbl.inspect }
context "with a name" do
let(dbl) { FooBarDouble.new }
it "contains the double type" do
expect(string).to contain("NullDouble")
end
it "contains the double name" do
expect(string).to contain("dbl-name")
end
it "contains the object ID" do
expect(string).to contain(dbl.object_id.to_s(16))
end
end
context "without a name" do
let(dbl) { EmptyDouble.new }
it "contains the double type" do
expect(string).to contain("NullDouble")
end
it "contains \"Anonymous\"" do
expect(string).to contain("Anonymous")
end
it "contains the object ID" do
expect(string).to contain(dbl.object_id.to_s(16))
end
end
end
end

View file

@ -1,165 +0,0 @@
require "../../spec_helper"
Spectator.describe Spectator::NullStub do
let(method_call) { Spectator::MethodCall.capture(:foo) }
let(location) { Spectator::Location.new(__FILE__, __LINE__) }
subject(stub) { described_class.new(:foo, location: location) }
it "stores the method name" do
expect(stub.method).to eq(:foo)
end
it "stores the location" do
expect(stub.location).to eq(location)
end
it "returns nil" do
expect(stub.call(method_call)).to be_nil
end
context Spectator::StubModifiers do
describe "#and_return(value)" do
let(arguments) { Spectator::Arguments.capture(/foo/) }
let(location) { Spectator::Location.new(__FILE__, __LINE__) }
let(original) { Spectator::NullStub.new(:foo, arguments, location) }
subject(stub) { original.and_return(42) }
it "produces a stub that returns a value" do
expect(stub.call(method_call)).to eq(42)
end
it "retains the method name" do
expect(stub.method).to eq(:foo)
end
it "retains the arguments constraint" do
expect(stub.constraint).to eq(arguments)
end
it "retains the location" do
expect(stub.location).to eq(location)
end
end
describe "#and_return(*values)" do
let(arguments) { Spectator::Arguments.capture(/foo/) }
let(location) { Spectator::Location.new(__FILE__, __LINE__) }
let(original) { Spectator::NullStub.new(:foo, arguments, location) }
subject(stub) { original.and_return(3, 2, 1, 0) }
it "produces a stub that returns values" do
values = Array.new(5) { stub.call(method_call) }
expect(values).to eq([3, 2, 1, 0, 0])
end
it "retains the method name" do
expect(stub.method).to eq(:foo)
end
it "retains the arguments constraint" do
expect(stub.constraint).to eq(arguments)
end
it "retains the location" do
expect(stub.location).to eq(location)
end
end
describe "#and_raise" do
let(arguments) { Spectator::Arguments.capture(/foo/) }
let(location) { Spectator::Location.new(__FILE__, __LINE__) }
let(original) { Spectator::NullStub.new(:foo, arguments, location) }
let(new_exception) { ArgumentError.new("Test argument error") }
subject(stub) { original.and_raise(new_exception) }
it "produces a stub that raises" do
expect { stub.call(method_call) }.to raise_error(ArgumentError, "Test argument error")
end
context "with a class and message" do
subject(stub) { original.and_raise(ArgumentError, "Test argument error") }
it "produces a stub that raises" do
expect { stub.call(method_call) }.to raise_error(ArgumentError, "Test argument error")
end
end
context "with a message" do
subject(stub) { original.and_raise("Test exception") }
it "produces a stub that raises" do
expect { stub.call(method_call) }.to raise_error(Exception, "Test exception")
end
end
context "with a class" do
subject(stub) { original.and_raise(ArgumentError) }
it "produces a stub that raises" do
expect { stub.call(method_call) }.to raise_error(ArgumentError)
end
end
it "retains the method name" do
expect(stub.method).to eq(:foo)
end
it "retains the arguments constraint" do
expect(stub.constraint).to eq(arguments)
end
it "retains the location" do
expect(stub.location).to eq(location)
end
end
end
describe "#===" do
subject { stub === call }
context "with a matching method name" do
let(call) { Spectator::MethodCall.capture(:foo, "foobar") }
it "returns true" do
is_expected.to be_true
end
end
context "with a different method name" do
let(call) { Spectator::MethodCall.capture(:bar, "foobar") }
it "returns false" do
is_expected.to be_false
end
end
context "with a constraint" do
let(constraint) { Spectator::Arguments.capture(/foo/) }
let(stub) { described_class.new(:foo, constraint) }
context "with a matching method name" do
let(call) { Spectator::MethodCall.capture(:foo, "foobar") }
it "returns true" do
is_expected.to be_true
end
context "with a non-matching arguments" do
let(call) { Spectator::MethodCall.capture(:foo, "baz") }
it "returns false" do
is_expected.to be_false
end
end
end
context "with a different method name" do
let(call) { Spectator::MethodCall.capture(:bar, "foobar") }
it "returns false" do
is_expected.to be_false
end
end
end
end
end

View file

@ -1,182 +0,0 @@
require "../../spec_helper"
Spectator.describe Spectator::ProcStub do
let(method_call) { Spectator::MethodCall.capture(:foo) }
let(location) { Spectator::Location.new(__FILE__, __LINE__) }
let(proc) { Proc(Spectator::AbstractArguments, Int32).new { @call_count += 1 } }
subject(stub) { described_class.new(:foo, proc, location: location) }
@call_count = 0
it "stores the method name" do
expect(stub.method).to eq(:foo)
end
it "stores the location" do
expect(stub.location).to eq(location)
end
it "calls the proc" do
expect(stub.call(method_call)).to eq(1)
end
it "calls the proc for each invocation" do
stub.call(method_call)
expect { stub.call(method_call) }.to change { @call_count }.from(1).to(2)
end
it "passed the original arguments" do
proc = Proc(Spectator::AbstractArguments, Spectator::AbstractArguments).new { |a| a }
stub = described_class.new(:foo, proc)
args = Spectator::Arguments.capture(42, bar: "baz")
call = Spectator::MethodCall.new(:foo, args)
captured = stub.call(call)
expect(captured).to eq(args)
end
context Spectator::StubModifiers do
describe "#and_return(value)" do
let(arguments) { Spectator::Arguments.capture(/foo/) }
let(location) { Spectator::Location.new(__FILE__, __LINE__) }
let(original) { Spectator::ProcStub.new(:foo, proc, arguments, location) }
subject(stub) { original.and_return(123) }
it "produces a stub that returns a value" do
expect(stub.call(method_call)).to eq(123)
end
it "retains the method name" do
expect(stub.method).to eq(:foo)
end
it "retains the arguments constraint" do
expect(stub.constraint).to eq(arguments)
end
it "retains the location" do
expect(stub.location).to eq(location)
end
end
describe "#and_return(*values)" do
let(arguments) { Spectator::Arguments.capture(/foo/) }
let(location) { Spectator::Location.new(__FILE__, __LINE__) }
let(original) { Spectator::ProcStub.new(:foo, proc, arguments, location) }
subject(stub) { original.and_return(3, 2, 1, 0) }
it "produces a stub that returns values" do
values = Array.new(5) { stub.call(method_call) }
expect(values).to eq([3, 2, 1, 0, 0])
end
it "retains the method name" do
expect(stub.method).to eq(:foo)
end
it "retains the arguments constraint" do
expect(stub.constraint).to eq(arguments)
end
it "retains the location" do
expect(stub.location).to eq(location)
end
end
describe "#and_raise" do
let(arguments) { Spectator::Arguments.capture(/foo/) }
let(location) { Spectator::Location.new(__FILE__, __LINE__) }
let(original) { Spectator::ProcStub.new(:foo, proc, arguments, location) }
let(new_exception) { ArgumentError.new("Test argument error") }
subject(stub) { original.and_raise(new_exception) }
it "produces a stub that raises" do
expect { stub.call(method_call) }.to raise_error(ArgumentError, "Test argument error")
end
context "with a class and message" do
subject(stub) { original.and_raise(ArgumentError, "Test argument error") }
it "produces a stub that raises" do
expect { stub.call(method_call) }.to raise_error(ArgumentError, "Test argument error")
end
end
context "with a message" do
subject(stub) { original.and_raise("Test exception") }
it "produces a stub that raises" do
expect { stub.call(method_call) }.to raise_error(Exception, "Test exception")
end
end
context "with a class" do
subject(stub) { original.and_raise(ArgumentError) }
it "produces a stub that raises" do
expect { stub.call(method_call) }.to raise_error(ArgumentError)
end
end
it "retains the method name" do
expect(stub.method).to eq(:foo)
end
it "retains the arguments constraint" do
expect(stub.constraint).to eq(arguments)
end
it "retains the location" do
expect(stub.location).to eq(location)
end
end
end
describe "#===" do
subject { stub === call }
context "with a matching method name" do
let(call) { Spectator::MethodCall.capture(:foo, "foobar") }
it "returns true" do
is_expected.to be_true
end
end
context "with a different method name" do
let(call) { Spectator::MethodCall.capture(:bar, "foobar") }
it "returns false" do
is_expected.to be_false
end
end
context "with a constraint" do
let(constraint) { Spectator::Arguments.capture(/foo/) }
let(stub) { Spectator::ValueStub.new(:foo, 42, constraint) }
context "with a matching method name" do
let(call) { Spectator::MethodCall.capture(:foo, "foobar") }
it "returns true" do
is_expected.to be_true
end
context "with a non-matching arguments" do
let(call) { Spectator::MethodCall.capture(:foo, "baz") }
it "returns false" do
is_expected.to be_false
end
end
end
context "with a different method name" do
let(call) { Spectator::MethodCall.capture(:bar, "foobar") }
it "returns false" do
is_expected.to be_false
end
end
end
end
end

View file

@ -1,93 +0,0 @@
require "../../spec_helper"
Spectator.describe Spectator::ReferenceMockRegistry do
subject(registry) { described_class.new }
let(obj) { "foobar" }
let(stub) { Spectator::ValueStub.new(:test, 42) }
let(stubs) { [stub] of Spectator::Stub }
let(no_stubs) { [] of Spectator::Stub }
let(call) { Spectator::MethodCall.capture(:method2, 5) }
let(calls) { [call] }
let(no_calls) { [] of Spectator::MethodCall }
it "initially has no stubs" do
expect(registry[obj].stubs).to be_empty
end
it "initially has no calls" do
expect(registry[obj].calls).to be_empty
end
it "stores stubs for an object" do
expect { registry[obj].stubs << stub }.to change { registry[obj].stubs }.from(no_stubs).to(stubs)
end
it "stores calls for an object" do
expect { registry[obj].calls << call }.to change { registry[obj].calls }.from(no_calls).to(calls)
end
it "isolates stubs between different objects" do
obj1 = "foo"
obj2 = "bar"
registry[obj2].stubs << Spectator::ValueStub.new(:obj2, 42)
expect { registry[obj1].stubs << stub }.to_not change { registry[obj2].stubs }
end
it "isolates calls between different objects" do
obj1 = "foo"
obj2 = "bar"
registry[obj2].calls << Spectator::MethodCall.capture(:method1, 42)
expect { registry[obj1].calls << call }.to_not change { registry[obj2].calls }
end
describe "#fetch" do
it "retrieves existing stubs" do
registry[obj].stubs << stub
expect(registry.fetch(obj) { no_stubs }.stubs).to eq(stubs)
end
it "stores stubs on the first retrieval" do
expect(registry.fetch(obj) { stubs }.stubs).to eq(stubs)
end
it "isolates stubs between different objects" do
obj1 = "foo"
obj2 = "bar"
registry[obj2].stubs << Spectator::ValueStub.new(:obj2, 42)
expect { registry.fetch(obj1) { no_stubs }.stubs }.to_not change { registry[obj2].stubs }
end
it "isolates calls between different objects" do
obj1 = "foo"
obj2 = "bar"
registry[obj2].calls << Spectator::MethodCall.capture(:method1, 42)
expect { registry.fetch(obj1) { no_stubs }.calls }.to_not change { registry[obj2].calls }
end
end
describe "#delete" do
it "clears stubs for an object" do
registry[obj].stubs << stub
expect { registry.delete(obj) }.to change { registry[obj].stubs }.from(stubs).to(no_stubs)
end
it "doesn't clear initial stubs provided with #fetch" do
registry[obj].stubs << Spectator::ValueStub.new(:stub2, 42)
expect { registry.delete(obj) }.to change { registry.fetch(obj) { stubs }.stubs }.to(stubs)
end
it "isolates stubs between different objects" do
obj1 = "foo"
obj2 = "bar"
registry[obj2].stubs << Spectator::ValueStub.new(:obj2, 42)
expect { registry.delete(obj1) }.to_not change { registry[obj2].stubs }
end
it "isolates calls between different objects" do
obj1 = "foo"
obj2 = "bar"
registry[obj2].calls << Spectator::MethodCall.capture(:method1, 42)
expect { registry.delete(obj1) }.to_not change { registry[obj2].calls }
end
end
end

View file

@ -1,93 +0,0 @@
require "../../spec_helper"
Spectator.describe Spectator::ValueMockRegistry do
subject(registry) { Spectator::ValueMockRegistry(Int32).new }
let(obj) { 42 }
let(stub) { Spectator::ValueStub.new(:test, 5) }
let(stubs) { [stub] of Spectator::Stub }
let(no_stubs) { [] of Spectator::Stub }
let(call) { Spectator::MethodCall.capture(:method2, 5) }
let(calls) { [call] }
let(no_calls) { [] of Spectator::MethodCall }
it "initially has no stubs" do
expect(registry[obj].stubs).to be_empty
end
it "initially has no calls" do
expect(registry[obj].calls).to be_empty
end
it "stores stubs for an object" do
expect { registry[obj].stubs << stub }.to change { registry[obj].stubs }.from(no_stubs).to(stubs)
end
it "stores calls for an object" do
expect { registry[obj].calls << call }.to change { registry[obj].calls }.from(no_calls).to(calls)
end
it "isolates stubs between different objects" do
obj1 = 1
obj2 = 2
registry[obj2].stubs << Spectator::ValueStub.new(:obj2, 42)
expect { registry[obj1].stubs << stub }.to_not change { registry[obj2].stubs }
end
it "isolates calls between different objects" do
obj1 = 1
obj2 = 2
registry[obj2].calls << Spectator::MethodCall.capture(:method1, 42)
expect { registry[obj1].calls << call }.to_not change { registry[obj2].calls }
end
describe "#fetch" do
it "retrieves existing stubs" do
registry[obj].stubs << stub
expect(registry.fetch(obj) { no_stubs }.stubs).to eq(stubs)
end
it "stores stubs on the first retrieval" do
expect(registry.fetch(obj) { stubs }.stubs).to eq(stubs)
end
it "isolates stubs between different objects" do
obj1 = 1
obj2 = 2
registry[obj2].stubs << Spectator::ValueStub.new(:obj2, 42)
expect { registry.fetch(obj1) { no_stubs }.stubs }.to_not change { registry[obj2].stubs }
end
it "isolates calls between different objects" do
obj1 = 1
obj2 = 2
registry[obj2].calls << Spectator::MethodCall.capture(:method1, 42)
expect { registry.fetch(obj1) { no_stubs }.calls }.to_not change { registry[obj2].calls }
end
end
describe "#delete" do
it "clears stubs for an object" do
registry[obj].stubs << stub
expect { registry.delete(obj) }.to change { registry[obj].stubs }.from(stubs).to(no_stubs)
end
it "doesn't clear initial stubs provided with #fetch" do
registry[obj].stubs << Spectator::ValueStub.new(:stub2, 42)
expect { registry.delete(obj) }.to change { registry.fetch(obj) { stubs }.stubs }.to(stubs)
end
it "isolates stubs between different objects" do
obj1 = 1
obj2 = 2
registry[obj2].stubs << Spectator::ValueStub.new(:obj2, 42)
expect { registry.delete(obj1) }.to_not change { registry[obj2].stubs }
end
it "isolates calls between different objects" do
obj1 = 1
obj2 = 2
registry[obj2].calls << Spectator::MethodCall.capture(:method1, 42)
expect { registry.delete(obj1) }.to_not change { registry[obj2].calls }
end
end
end

Some files were not shown because too many files have changed in this diff Show more