{"id":13878638,"url":"https://github.com/motine/fixpoints","last_synced_at":"2025-07-16T14:32:35.976Z","repository":{"id":56846595,"uuid":"294489863","full_name":"motine/fixpoints","owner":"motine","description":"Fixpoints enables saving, restoring and comparing the database state before \u0026 after tests.","archived":false,"fork":false,"pushed_at":"2022-08-24T06:56:44.000Z","size":65,"stargazers_count":6,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-09T15:47:08.987Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","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/motine.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-09-10T18:25:10.000Z","updated_at":"2024-06-09T13:56:28.000Z","dependencies_parsed_at":"2022-09-06T18:01:04.853Z","dependency_job_id":null,"html_url":"https://github.com/motine/fixpoints","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/motine/fixpoints","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/motine%2Ffixpoints","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/motine%2Ffixpoints/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/motine%2Ffixpoints/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/motine%2Ffixpoints/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/motine","download_url":"https://codeload.github.com/motine/fixpoints/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/motine%2Ffixpoints/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265072310,"owners_count":23706951,"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":[],"created_at":"2024-08-06T08:01:55.427Z","updated_at":"2025-07-16T14:32:35.713Z","avatar_url":"https://github.com/motine.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# Fixpoints\n\nFixpoints enables saving, restoring and comparing the database state before \u0026 after tests.\n\nThis gem came about during my time at [Netskin GmbH](https://www.netskin.com/en). Check it out, we do great (Rails) work there.\n\n## Motivation\n\nWhen running behavior tests, we seed the database with a defined snapshot called fixpoint.\nWe do run the behavior test and save the resulting database state as another fixpoint.\nThis method allows testing complex business processes in legacy applications without having to implement fixtures/factories upfront.\nBy building one fixpoint on top of another, we can ensure that the process chain works without any gaps.\nComparing each resulting database state at the end of a test with a previously recorded state ensures that refactoring did not have unintended side effects.\n\n**Advantages**\n\n- No need to write fixtures or factories\n- discover which records were created/changed by the test’s actions by reading the fixpoint file (YAML)\n- get notified about differences in database state (i.e. unintended side effects) after refactoring something\n- allow version control to save the \"ground truth\" at the end of a test\n\nPlease check out the full article: [Behavior-Driven Test Data](https://tomrothe.de/posts/behaviour-driven-test-data.html).\n\n## Installation\n\nAdd this line to your application's Gemfile: `gem 'fixpoints'` and make sure to add:\n\n```ruby\n# rails_helper.rb\nRSpec.configure do |config|\n  # ...\n  config.include FixpointTestHelpers\nend\n```\n\n## Usage\n\nWe save the fixpoint (database snapshot) after the test. Other tests can build on them.\n\nA fixpoint is a snapshot of the database contents as YAML file.\nIt is saved to the `spec/fixpoints` folder.\nThe file contains a mapping of table names to a list if their records.\nEmpty tables are stripped from files.\n\n**Order \u0026 Bootstrapping** We need to mind the order though.\nWhen bootstrapping (when there is no fixpoints saved to the disk yet), we need to make sure that all tests that depend on a certain fixpoint run _after_ it was stored.\nIn a single RSpec file, you can use the order in which the tests are defined (`RSpec.describe 'MyFeature', order: :defined do`).\nHowever, tests in groups might follow a slightly different order (see [RSpec Docs](https://relishapp.com/rspec/rspec-core/docs/configuration/overriding-global-ordering))\n\n```ruby\nRSpec.describe 'User Flow', order: :defined do # !!! mind the order here !!!\n  it 'registers a user' do\n    visit new_user_path\n    fill_in 'Name', with: 'Tom'\n    click_on 'Save'\n\n    store_fixpoint_unless_present :registered_user\n    # creates a YAML file containing all records (/spec/fixpoints/registred_user.yml)\n  end\n\n  it 'posts an item' do\n    restore_fixpoint :registered_user\n    \n    user = User.find_by(name: 'Hans')\n    visit new_item_path(user)\n    fill_in 'Item', with: '...'\n    click_on 'Post'\n\n    compare_fixpoint(:item_posted, store_fixpoint_and_fail: true)\n    # compares the database state with the previously saved fixpoint and\n    # raises if there is a difference. when there is no previous fixpoint,\n    # it writes the fixpoint and fails the test (so it can be re-run)\n  end\nend\n```\n\n**Changes** If you did a lot of changes to a test, you can remove a fixpoint file from its directory.\nIt will be recreated when the test producing it runs again.\nDon't forget re-running the tests based on it because their fixpoints might have to change too.  \nExample: You need to add something to the database's `seeds.rb`. All subsequent fixpoints are missing the required entry.\nTo update all fixpoints, just remove the whole `spec/fixpoints` folder and re-run all tests. Now all fixpoints should be updated.\nBe careful though, don't just remove the fixpoints if you are not sure what is going on.\nA change in a fixpoint might point to an unintended change in code.\n\nWe need to be be careful to use `let` and `let!` with factories.\nRecords might be created twice when using create in there (once by the fixpoint and once by the factory).\n\n**Ignoring columns** Often you might want to add more columns to ignore (e.g. login time stamps):\n\n```ruby\nlet(:ignored_fixpoint_columns) { [:updated_at, :created_at, users: [:last_login_at] }\n# ignores timestamps for all tables, and last_login_at for the users table\n\nit 'logs in' do\n  restore_fixpoint :registered_user\n  # ...\n  compare_fixpoint(:registered_user, ignored_fixpoint_columns)\n  # asserts that there is no change\nend\n```\n\n**Incremental** By the default the `FixpointTestHelpers` use the `IncrementalFixpoint` instead of the more verbose `Fixpoint` version.\nThis means that only changes are saved to the YAML file.\nIn order to achieve this, we must make sure that we let the store function know who daddy is.\n\n```ruby\n  it 'posts an item' do\n    restore_fixpoint :registered_user\n    # ...\n    compare_fixpoint(fixname, store_fixpoint_and_fail: true, parent_fixname: :registered_user)\n    # now only changes to compared to the previous fixpoint are stored\n    # instead of using the name of the last restored fixpoint, you can also use `:last_restored`\n  end\n```\n\n**Multiple Databases** If an application uses multiple databases, you can use the optional `connection` parameter\nto specify the database connection to use.\n\n```ruby\n  it 'posts an item' do\n    restore_fixpoint :registered_user, connection: ActiveRecord::Base.connection\n    # ...\n  end\n```\n\n**Exclude Tables** If a database contains tables that are irrelevant to your tests, you can use the optional `exclude_tables` parameter\nto specify a set of tables to exclude from the fixpoint.\n\n```ruby\n  it 'excludes versions' do\n    store_fixpoint_unless_present :registered_user, exclude_tables: ['versions']\n    # ...\n  end\n```\n\n## Limitations \u0026 Known issues\n\n- The records in tables are ordered by their id.\n    If there is no id for a table, we use database's order (what the SELECT query returns).\n    This order may be instable.\n- We do not clean the database after each test, depending on your cleaning strategy (e.g. transaction), we might leak primary key sequence counters from one test to another.\n    If you have problems try running `Fixpoint.reset_pk_sequences!` and create am issue, so we can investigate.\n- Under certain conditions you may get `duplicate key value violates unique constraint` because the primary key sequences are not updated correctly.\n    If this happens, just add a `Fixpoint.reset_pk_sequences!` at the beginning of your test. We need to dig a little deeper here at some point...\n\n# Development\n\n```bash\ndocker run --rm -ti -v (pwd):/app -w /app ruby:2.7 bash\nbundle install\nrspec\npry # require_relative 'lib/fixpoints.rb'\n\ngem build\ngem install fixpoints-0.1.0.gem\npry -r fixpoints\ngem uninstall fixpoints\ngem push fixpoints\n```\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/motine/fixpoints.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmotine%2Ffixpoints","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmotine%2Ffixpoints","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmotine%2Ffixpoints/lists"}