⬆️ ⬇️

Fixtures in Rails and their alternatives

I have been sitting on the rails not so long ago, but, nevertheless, I have already managed to dig in something. One of the topics that had to be dealt with quite carefully was the fixtures and their alternatives in the rails tests.



A bit about fixtures themselves

A small review for those who are not quite in the subject. Fixes in Rails is a piece that allows you to drive into the test database the previously prepared data that is used by the objects under test. In the test code itself, it is enough to call the fixtures :users method and it will automatically load all the data for objects of the User class. For more information, you should read the manual (which, incidentally, is short).



Why developers do not like fixtures?

Despite the fact that this tool was designed to help developers, solving one problem, he created several others. Here are the main discontent with fixtures, which are usually expressed in blogs:

  1. Fixtures are specific to each test, but are nevertheless available to each of the tests. That is, if in one test I need some data for User objects, and in the other there are several others, it will be impossible to do this with standard fixtures.
  2. If the records for the database are not very small, fixtures become much more difficult to model such object relations as has_one, has_many, habtm and has_many: through - you need to keep track of the correct values ​​of foreign keys. Although, in fact, the standard mechanism provides a convenient way to avoid such hemorrhoids (see below), this is often forgotten.
  3. Non-visibility Some developers want to see clearly what data will be used directly in the test code.


What are the alternatives?

As an alternative to fixtures, you can first of all create objects directly in tests, like so: %w("Tom Bill Frank").each { |name| User.create(:first_name => name) } %w("Tom Bill Frank").each { |name| User.create(:first_name => name) } . I tried to do this, but the disadvantage here is that after each test you will have to somehow manually clear the test database. If, at the same time, such tests are run with tests that still use fixtures, then conflicts can occur - fixtures use transactions to populate / clear the database, so before performing a non-test test it may turn out that the base is not actually cleared. (For example, because of this, I had a non-test test running alone and successfully passing it, but running along with other tests gave an error).

')

The community went further and came up with several solutions ( 1 , 2 ) that simplify the creation of objects with data to bypass fixtures.



Why, nevertheless, it may be more convenient to use fixtures?

Imagine that you are adding a new field to your model and validating its presence. Now imagine that you have 10 non-texture tests in which objects are created and populated. Not too much pleasure awaits you when you will edit all these 10 tests (the problem, by the way, is quite vital - this is exactly what happened when I started to build the plugin restful_authentication into the model and added the password field). Unit tests are then unit tests, which should not depend on anything else and should use only the data that is implied to test the model's functionality.



Therefore, it is easier to actually solve the shortcomings mentioned in the beginning of the article within the framework of the fixtures themselves than to switch to a solution that creates new problems. Here's how:

  1. The problem of the specificity of fixtures for each test is solved by the FixtureScenarios plugin. Now in each test it is enough to specify the scenario :logging and all fixtures will be loaded from the fixtures / logging directory. According to the link above, the article mentions an additional plugin FixtureScenarioBuilder - I tried to use it, but firstly, at that time there was obviously a bug, because of which all tests were falling, and secondly - there wasn’t much point in it, because Dynamic creation of a handful of fixtures in three lines can be organized directly in YAML files using ERb (see the fixtures “Dynamic fixtures with ERb” section of mana ).
  2. The problem of how to keep track of a bunch of foreign keys records is solved in the standard way provided by fixtures:



    ### in pirates.yml

    reginald:

    name: Reginald the Pirate

    monkey: george



    ### in monkeys.yml

    george:

    name: George the Monkey

    pirate: reginald



    In this case, the id of the record is not set explicitly (by the way, I don’t see any reason to do it ever at all in fixtures), but is assigned automatically. Rails also automatically substitute the corresponding id for george and reginald. And now we have a has_one and belongs_to relationship. More details about using this approach are again written in the manual , in the section “Advanced YAML Fixtures”. Particularly powerful this approach can be used by combining it with the generation of records using ERb.

  3. The problem of clarity , in my opinion, is also solved by using the FixtureScenarios plugin. Named scripts allow you to think once about what data will be loaded for this script and not to return to this question in the future, simply by controlling which scenarios are used in the test. If necessary, you can add new scripts, which, by the way, can be nested (then, for example, the data from the fixtures / logging will be loaded first, and then from the fixtures / logging / admin data).


I would be grateful for comments, additions and opinions of professionals.

Source: https://habr.com/ru/post/30387/



All Articles