mirror of
https://gitea.invidious.io/iv-org/shard-spectator.git
synced 2024-08-15 00:53:35 +00:00
Fresh start
This commit is contained in:
parent
287758e6af
commit
10a0ccc8bd
292 changed files with 43 additions and 26536 deletions
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
273
ARCHITECTURE.md
273
ARCHITECTURE.md
|
@ -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*
|
530
CHANGELOG.md
530
CHANGELOG.md
|
@ -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
|
204
CONTRIBUTING.md
204
CONTRIBUTING.md
|
@ -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
12
LICENSE
|
@ -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
426
README.md
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue