{"id":14991255,"url":"https://github.com/icy-arctic-fox/spectator","last_synced_at":"2025-10-05T20:15:42.632Z","repository":{"id":45189379,"uuid":"179995896","full_name":"icy-arctic-fox/spectator","owner":"icy-arctic-fox","description":"Feature-rich testing framework for Crystal inspired by RSpec.","archived":false,"fork":false,"pushed_at":"2025-10-05T04:30:46.000Z","size":3154,"stargazers_count":103,"open_issues_count":9,"forks_count":5,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-10-05T06:14:02.709Z","etag":null,"topics":["bdd","crystal","hacktoberfest","rspec","spec","tdd","testing"],"latest_commit_sha":null,"homepage":"https://gitlab.com/arctic-fox/spectator","language":"Crystal","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/icy-arctic-fox.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-04-07T16:57:19.000Z","updated_at":"2025-09-27T21:28:13.000Z","dependencies_parsed_at":"2024-06-28T05:24:33.258Z","dependency_job_id":"3343f71e-ad5d-4f93-908c-6715de75bd71","html_url":"https://github.com/icy-arctic-fox/spectator","commit_stats":{"total_commits":2149,"total_committers":9,"mean_commits":"238.77777777777777","dds":"0.0055839925546765645","last_synced_commit":"dcaa05531a3bba4855b8801b6a30bbd6c4e85cbc"},"previous_names":[],"tags_count":71,"template":false,"template_full_name":null,"purl":"pkg:github/icy-arctic-fox/spectator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icy-arctic-fox%2Fspectator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icy-arctic-fox%2Fspectator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icy-arctic-fox%2Fspectator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icy-arctic-fox%2Fspectator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/icy-arctic-fox","download_url":"https://codeload.github.com/icy-arctic-fox/spectator/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icy-arctic-fox%2Fspectator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278510951,"owners_count":25999013,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-05T02:00:06.059Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bdd","crystal","hacktoberfest","rspec","spec","tdd","testing"],"created_at":"2024-09-24T14:22:01.812Z","updated_at":"2025-10-05T20:15:42.603Z","avatar_url":"https://github.com/icy-arctic-fox.png","language":"Crystal","funding_links":[],"categories":[],"sub_categories":[],"readme":"Spectator\n=========\n\nSpectator is a fully-featured spec-based test framework for Crystal.\nIt mimics features from [RSpec](http://rspec.info/).\nDevelopers coming from Ruby and RSpec will feel right at home.\nSpectator provides additional functionality to make testing easier and more fluent.\n\n**Goal:**\n\nSpectator is designed to:\n\n- Reduce complexity of test code.\n- Remove boilerplate from tests.\n- Lower the difficulty of writing non-trivial tests.\n- Provide an elegant syntax that is easy to read and understand.\n- Provide common utilities that the end-user would otherwise need to write.\n\nInstallation\n------------\n\nAdd this to your application's `shard.yml`:\n\n```yaml\ndevelopment_dependencies:\n  spectator:\n    gitlab: arctic-fox/spectator\n    version: ~\u003e 0.12.0\n```\n\nUsage\n-----\n\nIf it doesn't exist already, create a `spec/spec_helper.cr` file.\nIn it, place the following:\n\n```crystal\nrequire \"../src/*\"\nrequire \"spectator\"\n```\n\nThis will include Spectator and the source code for your shard.\nNow you can start writing your specs.\nThe syntax is the same as what you would expect from modern RSpec.\nThe \"expect\" syntax is recommended and the default, however the \"should\" syntax is also available.\nYour specs must be wrapped in a `Spectator.describe` block.\nAll other blocks inside the top-level block may use `describe` and `context` without the `Spectator.` prefix.\n\nHere's a minimal spec to demonstrate:\n\n```crystal\nrequire \"./spec_helper\"\n\nSpectator.describe String do\n  subject { \"foo\" }\n\n  describe \"#==\" do\n    context \"with the same value\" do\n      let(value) { subject.dup }\n\n      it \"is true\" do\n        is_expected.to eq(value)\n      end\n    end\n\n    context \"with a different value\" do\n      let(value) { \"bar\" }\n\n      it \"is false\" do\n        is_expected.to_not eq(value)\n      end\n    end\n  end\nend\n```\n\nIf you find yourself trying to shoehorn in functionality\nor unsure how to write a test, please create an [issue](https://gitlab.com/arctic-fox/spectator/issues/new) for it.\nThe goal is to make it as easy as possible to write specs and keep your code clean.\nWe may come up with a solution or even introduce a feature to support your needs.\n\n**NOTE:** Due to the way this shard uses macros,\nyou may find that some code you would expect to work, or works in other spec libraries, creates syntax errors.\nIf you run into this, please create an issue so that we may try to resolve it.\n\nFeatures\n--------\n\nSpectator has all of the basic functionality for BDD.\nFor full documentation on what it can do, please visit the [wiki](https://gitlab.com/arctic-fox/spectator/wikis/home).\n\n### Contexts\n\nThe DSL supports arbitrarily nested contexts.\nContexts can have values defined for multiple tests (`let` and `subject`).\nAdditionally, hooks can be used to ensure any initialization or cleanup is done (`before`, `after`, and `around`).\nPre- and post-conditions can be used to ensure code contracts are kept.\n\n```crystal\n# Initialize the database before running the tests in this context.\nbefore_all { Database.init }\n\n# Teardown the database and cleanup after tests in the is context finish.\nafter_all { Database.cleanup }\n\n# Before each test, add some rows to the database.\nlet(row_count) { 5 }\nbefore_each do\n  row_count.times { Database.insert_row }\nend\n\n# Remove the rows after the test to get a clean slate.\nafter_each { Database.clear }\n\ndescribe \"#row_count\" do\n  it \"returns the number of rows\" do\n    expect(Database.row_count).to eq(row_count)\n  end\nend\n```\n\nSpectator has different types of contexts to reduce boilerplate.\nOne is the `sample` context.\nThis context type repeats all tests (and contexts within) for a set of values.\nFor instance, some feature should behave the same for different input.\nHowever, some inputs might cause problems, but should behave the same.\nAn example is various strings (empty strings, quoted strings, strings with non-ASCII, etc),\nand numbers (positive, negative, zero, NaN, infinity).\n\n```crystal\n# List of integers to test against.\ndef various_integers\n  [-7, -1, 0, 1, 42]\nend\n\n# Repeat nested tests for every value in `#various_integers`.\nsample various_integers do |int|\n  # Example that checks if a fictitious method `#format` converts to strings.\n  it \"formats correctly\" do\n    expect(format(int)).to eq(int.to_s)\n  end\nend\n```\n\nAnother context type is `provided`.\nThis context drastically reduces the amount of code needed in some scenarios.\nIt can be used where one (or more inputs) changes the output of multiple methods.\nThe `provided` context gives a concise syntax for this use case.\n\n```crystal\nsubject(user) { User.new(age) }\n\n# Each expression in the `provided` block is its own test.\nprovided age = 10 do\n  expect(user.can_drive?).to be_false\n  expect(user.can_vote?).to be_false\nend\n\nprovided age = 16 do\n  expect(user.can_drive?).to be_true\n  expect(user.can_vote?).to be_false\nend\n\nprovided age = 18 do\n  expect(user.can_drive?).to be_true\n  expect(user.can_vote?).to be_true\nend\n```\n\n### Assertions\n\nSpectator supports two formats for assertions (expectations).\nThe preferred format is the \"expect syntax\".\nThis takes the form:\n\n```crystal\nexpect(THIS).to eq(THAT)\n```\n\nThe other format, \"should syntax\" is used by Crystal's default Spec.\n\n```\nTHIS.should eq(THAT)\n```\n\nThe first format doesn't monkey-patch the `Object` type.\nAnd as a bonus, it captures the expression or variable passed to `expect()`.\nFor instance, compare these two tests:\n\n```crystal\nfoo = \"Hello world\"\nfoo.size.should eq(12) # Wrong on purpose!\n```\n\nProduces this error output:\n\n```text\nFailure: 11 does not equal 12\n\n  expected: 11\n    actual: 12\n```\n\nWhich is reasonable, but where did 11 come from?\nAlternatively, with the \"expect syntax\":\n\n```crystal\nfoo = \"Hello world\"\nexpect(foo.size).to eq(12) # Wrong on purpose!\n```\n\nProduces this error output:\n\n```text\nFailure: foo.size does not equal 12\n\n  expected: 12\n    actual: 11\n```\n\nThis makes it clearer what was being tested and failed.\n\n### Matchers\n\nSpectator has a variety of matchers for assertions.\nThese are named in such a way to help tests read as plain English.\nMatchers can be used on any value or block.\n\nThere are typical matchers for testing equality: `eq` and `ne`.\nAnd matchers for comparison: `\u003c`, `\u003c=`, `\u003e`, `\u003e=`, `be_within`.\nThere are matchers for checking contents of collections:\n`contain`, `have`, `start_with`, `end_with`, `be_empty`, `have_key`, and more.\nSee the [wiki](https://gitlab.com/arctic-fox/spectator/wikis/Matchers) for a full list of matchers.\n\n### Running\n\nSpectator supports multiple options for running tests.\n\"Fail fast\" aborts on the first test failure.\n\"Fail blank\" fails if there are no tests.\nTests can be filtered by their location and name.\nAdditionally, tests can be randomized.\nSpectator can be configured with command-line arguments,\na configure block in a `spec_helper.cr` file, and `.spectator` configuration file.\n\n```crystal\nSpectator.configure do |config|\n  config.fail_blank # Fail on no tests.\n  config.randomize  # Randomize test order.\n  config.profile    # Display slowest tests.\nend\n```\n\n### Mocks and Doubles\n\nSpectator supports an extensive mocking feature set via two types - mocks and doubles.\nMocks are used to override behavior in existing types.\nDoubles are objects that stand-in when there are no type restrictions.\nStubs can be defined on both which control how methods behave.\n\n```crystal\nabstract class Interface\n  abstract def invoke(thing) : String\nend\n\n# Type being tested.\nclass Driver\n  def do_something(interface : Interface, thing)\n    interface.invoke(thing)\n  end\nend\n\nSpectator.describe Driver do\n  # Define a mock for Interface.\n  mock Interface\n\n  # Define a double that the interface will use.\n  double(:my_double, foo: 42)\n\n  it \"does a thing\" do\n    # Create an instance of the mock interface.\n    interface = mock(Interface)\n    # Indicate that `#invoke` should return \"test\" when called.\n    allow(interface).to receive(:invoke).and_return(\"test\")\n\n    # Create an instance of the double.\n    dbl = double(:my_double)\n    # Call the mock method.\n    subject.do_something(interface, dbl)\n    # Verify everything went okay.\n    expect(interface).to have_received(:invoke).with(dbl)\n  end\nend\n```\n\nFor details on mocks and doubles, see the [wiki](https://gitlab.com/arctic-fox/spectator/-/wikis/Mocks-and-Doubles).\n\n### Output\n\nSpectator matches Crystal's default Spec output with some minor changes.\nJUnit and TAP are also supported output formats.\nThere are also highly detailed JSON and HTML outputs.\n\nDevelopment\n-----------\n\nThis shard is still in active development.\nNew features are being added and existing functionality improved.\nSpectator is well-tested, but may have some yet-to-be-found bugs.\n\n### Feature Progress\n\nIn no particular order, features that have been implemented and are planned.\nItems not marked as completed may have partial implementations.\n\n- [ ] DSL\n    - [X] `describe` and `context` blocks\n    - [X] Contextual values with `let`, `let!`, `subject`, `described_class`\n    - [X] Test multiple and generated values - `sample`, `random_sample`\n    - [X] Concise syntax - `provided` (was the now deprecated `given`)\n    - [X] Before and after hooks - `before_each`, `before_all`, `after_each`, `after_all`, `around_each`\n    - [X] Pre- and post-conditions - `pre_condition`, `post_condition`\n    - [ ] Other hooks - `on_success`, `on_failure`, `on_error`\n    - [X] One-liner syntax\n    - [X] Should syntax - `should`, `should_not`\n    - [X] Helper methods and modules\n    - [ ] Aliasing - custom example group types with preset attributes\n    - [X] Pending tests - `pending`\n    - [ ] Shared examples - `behaves_like`, `include_examples`\n    - [X] Deferred expectations - `to_eventually`, `to_never`\n- [ ] Matchers\n    - [X] Equality matchers - `eq`, `ne`, `be ==`, `be !=`\n    - [X] Comparison matchers - `be \u003c`, `be \u003c=`, `be \u003e`, `be \u003e=`, `be_within[.of]`, `be_close`\n    - [X] Type matchers - `be_a`, `respond_to`\n    - [ ] Collection matchers\n      - [X] `contain`\n      - [X] `have`\n      - [X] `contain_exactly`\n      - [X] `contain_exactly.in_any_order`\n      - [X] `match_array`\n      - [X] `match_array.in_any_order`\n      - [X] `start_with`\n      - [X] `end_with`\n      - [X] `be_empty`\n      - [X] `have_key`\n      - [X] `have_value`\n      - [X] `all`\n      - [ ] `all_satisfy`\n    - [X] Truthy matchers - `be`, `be_true`, `be_truthy`, `be_false`, `be_falsey`, `be_nil`\n    - [X] Error matchers - `raise_error`\n    - [ ] Yield matchers - `yield_control[.times]`, `yield_with_args[.times]`, `yield_with_no_args[.times]`, `yield_successive_args`\n    - [ ] Output matchers - `output[.to_stdout|.to_stderr]`\n    - [X] Predicate matchers - `be_x`, `have_x`\n    - [ ] Misc. matchers\n      - [X] `match`\n      - [ ] `satisfy`\n      - [X] `change[.by|.from[.to]|.to|.by_at_least|.by_at_most]`\n      - [X] `have_attributes`\n    - [ ] Compound - `and`, `or`\n- [ ] Mocks and Doubles\n    - [X] Mocks (Stub real types) - `mock TYPE { }`\n    - [X] Doubles (Stand-ins for real types) - `double NAME { }`\n    - [X] Method stubs - `allow().to receive()`, `allow().to receive().and_return()`\n    - [X] Spies - `expect().to have_received()`\n    - [X] Message expectations - `expect().to have_received().at_least()`\n    - [X] Argument expectations - `expect().to have_received().with()`\n    - [ ] Message ordering - `expect().to have_received().ordered`\n    - [X] Null doubles\n- [X] Runner\n    - [X] Fail fast\n    - [X] Test filtering - by name, context, and tags\n    - [X] Fail on no tests\n    - [X] Randomize test order\n    - [X] Dry run - for validation and checking formatted output\n    - [X] Config block in `spec_helper.cr`\n    - [X] Config file - `.spectator`\n- [X] Reporter and formatting\n    - [X] RSpec/Crystal Spec default\n    - [X] JSON\n    - [X] JUnit\n    - [X] TAP\n    - [X] HTML\n\n### How it Works (in a nutshell)\n\nThis shard makes extensive use of the Crystal macro system to build classes and modules.\nEach `describe` and `context` block creates a new class that inherits its parent.\nThe `it` block creates an method.\nAn instance of the group class is created to run the test.\nEach group class includes all test values and hooks.\n\nContributing\n------------\n\n1. Fork it (GitHub \u003chttps://github.com/icy-arctic-fox/spectator/fork\u003e or GitLab \u003chttps://gitlab.com/arctic-fox/spectator/fork/new\u003e)\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull/Merge Request\n\nPlease make sure to run `crystal tool format` before submitting.\nThe CI build checks for properly formatted code.\n[Ameba](https://crystal-ameba.github.io/) is run to check for code style.\n\nDocumentation is automatically generated and published to GitLab pages.\nIt can be found here: https://arctic-fox.gitlab.io/spectator\n\nThis project's home is (and primarily developed) on [GitLab](https://gitlab.com/arctic-fox/spectator).\nA mirror is maintained to [GitHub](https://github.com/icy-arctic-fox/spectator).\nIssues, pull requests (merge requests), and discussion are welcome on both.\nMaintainers will ensure your contributions make it in.\n\nFor more information, see: [CONTRIBUTING.md](CONTRIBUTING.md)\n\n### Testing\n\nTests must be written for any new functionality.\n\nThe `spec/` directory contains feature tests as well as unit tests.\nThese demonstrate small bits of functionality.\nThe feature tests are grouped into sub directories based on their type, they are:\n\n- docs/ - Example snippets from Spectator's documentation.\n- rspec/ - Examples from RSpec's documentation modified slightly to work with Spectator.\n  See: https://relishapp.com/rspec/\n  Additional sub directories in this directory represent the modules/projects of RSpec.\n\nThe other directories are for unit testing various parts of Spectator.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficy-arctic-fox%2Fspectator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ficy-arctic-fox%2Fspectator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficy-arctic-fox%2Fspectator/lists"}