{"id":22210526,"url":"https://github.com/nicolab/crystal-testify","last_synced_at":"2025-03-25T05:35:26.118Z","repository":{"id":78147779,"uuid":"349812922","full_name":"Nicolab/crystal-testify","owner":"Nicolab","description":"Testing utilities for Crystal lang specs. OOP abstraction for creating unit and integration tests.","archived":false,"fork":false,"pushed_at":"2021-03-26T19:19:36.000Z","size":62,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-30T05:15:25.577Z","etag":null,"topics":["crystal","crystal-lang","integration","integration-testing","spec","test","unit-testing","xunit"],"latest_commit_sha":null,"homepage":"https://nicolab.github.io/crystal-testify/","language":"Crystal","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Nicolab.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":"Nicolab","patreon":"nicolab","custom":"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick\u0026hosted_button_id=PGRH4ZXP36GUC"}},"created_at":"2021-03-20T19:05:03.000Z","updated_at":"2023-08-27T11:25:00.000Z","dependencies_parsed_at":"2023-07-27T04:16:10.503Z","dependency_job_id":null,"html_url":"https://github.com/Nicolab/crystal-testify","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nicolab%2Fcrystal-testify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nicolab%2Fcrystal-testify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nicolab%2Fcrystal-testify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nicolab%2Fcrystal-testify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Nicolab","download_url":"https://codeload.github.com/Nicolab/crystal-testify/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245407758,"owners_count":20610232,"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","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":["crystal","crystal-lang","integration","integration-testing","spec","test","unit-testing","xunit"],"created_at":"2024-12-02T20:12:59.623Z","updated_at":"2025-03-25T05:35:26.110Z","avatar_url":"https://github.com/Nicolab.png","language":"Crystal","funding_links":["https://github.com/sponsors/Nicolab","https://patreon.com/nicolab","https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick\u0026hosted_button_id=PGRH4ZXP36GUC"],"categories":[],"sub_categories":[],"readme":"# Testify\n\n[![CI Status](https://github.com/Nicolab/crystal-testify/workflows/CI/badge.svg?branch=master)](https://github.com/Nicolab/crystal-testify/actions) [![GitHub release](https://img.shields.io/github/release/Nicolab/crystal-testify.svg)](https://github.com/Nicolab/crystal-testify/releases) [![Docs](https://img.shields.io/badge/docs-available-brightgreen.svg)](https://nicolab.github.io/crystal-testify/)\n\nTesting utilities for Crystal lang specs.\n\nSpecs or unit test style?\n\nThe both! Based on std's Crystal Spec, Testify is an OOP abstraction for creating unit and integration tests.\nThis allows structuring some tests in an objective of maintenability, extendability and reusability.\n\nSome tests require a Spec style:\n\n```crystal\nit \"should create an account and send a welcome email\" do\n  # ...\nend\n```\n\nOther tests require a unitary way:\n\n```crystal\ndef test_send_html_email\n  # ...\nend\n\ndef test_send_text_email\n  # ...\nend\n\ndef test_get_with_default_value\n  # ...\nend\n\ndef test_get_without_default_value\n  # ...\nend\n\ndef test_delete_by_id\n  # ...\nend\n```\n\nOther tests require the benefits of OOP. Advanced example:\n\n```crystal\n# Common tests for all model.\nabstract class ModelTest \u003c Testify::Test\n  def before_all\n    db.connect\n    db.create_tables\n  end\n\n  def after_all\n    db.clean\n  end\n\n  def before_each\n    db.init\n  end\n\n  def after_each\n    db.reset\n  end\n\n  abstract def db : DBHelper\n  abstract def model_class : Model.class\n  abstract def get_model_values : Hash\n  abstract def get_updated_model_values : Hash\n\n  @[Data(\"get_model_values\")]\n  def test_create(values, expected)\n    model = model_class.create(values)\n    model.should be_a Model\n    model.to_h should eq expected\n  end\n\n  @[Data(\"get_updated_model_values\")]\n  def test_update(values, expected)\n    model_class.create(get_model_values)\n    model_class.update(values).to_h.should eq expected\n    # ...\n  end\n\n  def test_delete_by_id\n    id = model_class.create(get_model_values)\n    id.should be_a(Int32)\n    model_class.delete(id).rows_affected.should eq 1\n    model_class.find?(id).should eq nil\n  end\n\n  def test_find_by_id\n    # ...\n  end\nend\n```\n\nIn this example, thanks to `ModelTest` class defined above.\nBecause we define a common behavior that can be used by all the models that inherit it.\n\nWith the main benefits:\n\n* Common lifecycle hooks (before_all, before_each, ...).\n* Some tests common to all models do not need to be repeated.\n* Clean structured fashion for all models.\n* Reusability (example: `AdminTest \u003c UserTest` that reuses common tests, states and `Data` source).\n\nCommon tests will be automatically executed (by inheritance):\n\n```crystal\n# Test cases for the User model.\nclass UserTest \u003c ModelTest\n  getter db : DBHelper = DBHelper.new\n  getter model_class : Model.class = User\n\n  # `Data` source.\n  def get_model_values : Hash\n    {\n      \"username\" =\u003e \"foo\",\n      \"email\" =\u003e \"hello@example.org\",\n      # ...\n    }\n  end\n\n  # Updated `Data` source.\n  def get_updated_model_values : Hash\n    user_h = get_model_values\n    user_h[\"username\"] = \"bar\"\n    user_h[\"email\"] = \"updated@example.org\"\n    user_h\n  end\n\n  # Just write other tests specific to the model User...\nend\n```\n\n## Installation\n\n1. Add the dependency to your `shard.yml`:\n\n```yaml\n   dependencies:\n     testify:\n       github: nicolab/crystal-testify\n       version: ~\u003e 1.0.1 # Check the latest version!\n```\n\n2. Run `shards install`\n\n## Usage\n\n📘 [API doc](https://nicolab.github.io/crystal-testify/).\n\n---\n\nBased and fully compliant with:\n\n* [Crystal Spec std's](https://crystal-lang.org/api/Spec.html)\n* [Testing Crystal Code](https://crystal-lang.org/reference/guides/testing.html)\n\n---\n\nDefine the test(s) class(es):\n\n```crystal\nrequire \"testify\"\n\nclass ExampleTest \u003c Testify::Test\n  @hey = \"Crystal is awesome!\"\n\n  def test_something\n    true.should eq true\n  end\n\n  def test_my_mood\n    @hey.should eq \"Crystal is awesome!\"\n  end\n\n  # ...\nend\n\nclass AnotherTest \u003c Testify::Test\n  def test_foo\n    true.should eq true\n    # ...\n  end\n\n  # ...\nend\n\n# Runs all test cases\nTestify.run_all\n```\n\nA `Test` class can be run alone:\n\n```crystal\n# Runs only ExampleTest tests\nExampleTest.run\n\n# Runs only AnotherTest tests\nAnotherTest.run\n```\n\nInternally, `Testify.run_all` executes the `run` method of each `Test` class defined.\n\n### POO\n\nAll benefits related to a class are available, like:\n\n* [macros: hooks](https://crystal-lang.org/reference/syntax_and_semantics/macros/hooks.html)\n* [finalize](https://crystal-lang.org/reference/syntax_and_semantics/finalize.html)\n* [annotations](https://crystal-lang.org/reference/syntax_and_semantics/annotations/index.html)\n* Macros, inheritance, modules, variables, methods, visibility, ... powerful 🚀\n\nUnder the hood:\n\n* A class defines a \"describe\" block.\n* Methods `test_` and `ftest_`, define a `it` block.\n* `ptest_` method, `Pending` and `Skip` annotations, define a `pending` block.\n* `ftest_` method and `Focus` annotation, add `focus: true` to a `it` block.\n* `Tags(\"foo\")` annotation on a `test` method, add `tags: \"foo\"` to a `it` block.\n* `Tags(\"foo\")` annotation on a `Test` class, add `tags: \"foo\"` to a `describe` block.\n\n### Lifecycle\n\nOptionally if you need life cycle hooks related to your tests.\n\n```crystal\nclass ExampleTest \u003c Testify::Test\n  def before_all\n    puts \"before_all\"\n  end\n\n  def before_each\n    puts \"before_each\"\n  end\n\n  def around_all(test)\n    puts \"around_all - before\"\n    test.run\n    puts \"around_all - after\"\n  end\n\n  def around_each(test)\n    puts \"around_each - before\"\n    test.run\n    puts \"around_each - after\"\n  end\n\n  def after_all\n    puts \"before_all\"\n  end\n\n  def after_each\n    puts \"before_each\"\n  end\n\n  # ...\nend\n```\n\n### Initialize\n\nOptionally if you need to initialize some variables.\n\n```crystal\nclass ExampleTest \u003c Testify::Test\n  # You can initialize variables, constants, contexts, ...\n\n  def initialize\n    # Configure here...\n  end\nend\n```\n\n### Test cases\n\nA test case, it's like:\n\n```crystal\nit \"my feature\" do\n  # ...\nend\n```\n\nExcept that this is written in the OOP way, in a method:\n\n```crystal\nclass ExampleTest \u003c Testify::Test\n  # A test case\n  def test_my_feature\n    # ...\n  end\n\n  # Another test.\n  def test_another_thing\n    # ...\n  end\n\n  # ...\nend\n```\n\n### Pending test / Skip test\n\nPending test, it's like:\n\n```crystal\npending \"my feature\" do\n  # ...\nend\n```\n\nThis can be written:\n\n```crystal\nclass ExampleTest \u003c Testify::Test\n  # Prefixed by `p`\n  def ptest_my_feature\n    # ...\n  end\n\n  # Pending test with `Pending` annotation.\n  @[Pending]\n  def test_my_feature\n    # ...\n  end\n\n  # Pending test with `Skip` annotation.\n  # Same as `Pending`, just another syntactic flavor.\n  @[Skip]\n  def test_my_feature\n    # ...\n  end\n\n  # ...\nend\n```\n\nA class can be skipped:\n\n```crystal\n@[Pending]\n# or @[Skip]\nclass ExampleTest \u003c Testify::Test\n  # ...\nend\n```\n\nIt's like marking a `describe` block as `pending`.\nAll tests contained in the current class will be skipped.\n\n\n### Focused test\n\nFocused test, it's like:\n\n```crystal\nit \"my feature\", focus: true do\n  # ...\nend\n```\n\nThis can be written:\n\n```crystal\nclass ExampleTest \u003c Testify::Test\n  # Prefixed by `f`\n  def ftest_my_feature\n  end\n\n  # Focused test with `Focus` annotation.\n  @[Focus]\n  def test_my_feature\n  end\nend\n```\n\nA class can be focused:\n\n```crystal\n@[Focus]\nclass ExampleTest \u003c Testify::Test\n  # ...\nend\n```\n\nIt's like focusing a `describe` block.\nOnly the tests contained in the focused class will be executed.\n\n### Tags\n\nTags test with `Tags` annotation, it's like:\n\n```crystal\nit \"my feature\", tags: \"slow\" do\n  # ...\nend\n```\n\nThis can be written:\n\n```crystal\nclass ExampleTest \u003c Testify::Test\n  @[Tags(\"slow\")]\n  def test_my_feature\n  end\nend\n```\n\nA class can be tagged:\n\n```crystal\n@[Tags(\"foo\")]\nclass ExampleTest \u003c Testify::Test\n  # ...\nend\n```\n\nIt's like tagging a `describe` block.\n\n### Tacker / Tracer\n\nTracking utilities to trace some behaviors (like a method call, an event listener, a `spawn`, a `Channel`, ...).\n\n## Development\n\nInstall dev dependencies:\n\n```sh\nshards install\n```\n\nRun:\n\n```sh\ncrystal spec\n```\n\nClean before commit:\n\n```sh\ncrystal tool format\n./bin/ameba\n```\n\n## Contributing\n\n1. Fork it (https://github.com/Nicolab/crystal-testify/fork)\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 Request\n\n## LICENSE\n\n[MIT](https://github.com/Nicolab/crystal-testify/blob/master/LICENSE) (c) 2021, Nicolas Talle.\n\n## Author\n\n| [![Nicolas Tallefourtane - Nicolab.net](https://www.gravatar.com/avatar/d7dd0f4769f3aa48a3ecb308f0b457fc?s=64)](https://github.com/sponsors/Nicolab) |\n|---|\n| [Nicolas Talle](https://github.com/sponsors/Nicolab) |\n| [![Make a donation via Paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick\u0026hosted_button_id=PGRH4ZXP36GUC) |\n\n### Inspi\n\n* [Unit testing](https://en.wikipedia.org/wiki/Unit_testing)\n* [Integration testing](https://en.wikipedia.org/wiki/Integration_testing)\n* [Data-Driven testing](https://en.wikipedia.org/wiki/Data-driven_testing)\n* [ASPEC class](https://github.com/athena-framework/spec/blob/a28a66ee0985d5aed7948183a5942c1c04848a31/src/test_case.cr)\n* [Testing Crystal Code](https://crystal-lang.org/reference/guides/testing.html)\n* [Crystal Spec std's](https://crystal-lang.org/api/Spec.html)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnicolab%2Fcrystal-testify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnicolab%2Fcrystal-testify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnicolab%2Fcrystal-testify/lists"}