Skip to content

Instantly share code, notes, and snippets.

@FCO
Last active December 11, 2022 14:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FCO/88c1ced4772f693619f4cda81e65597b to your computer and use it in GitHub Desktop.
Save FCO/88c1ced4772f693619f4cda81e65597b to your computer and use it in GitHub Desktop.

RedFactory

Since the elves started using Red (https://raku-advent.blog/2019/12/21/searching-for-a-red-gift/) they thought it was missing a better way of testing code that uses it. They tested it using several SQL files that would be used before each test to populate the database with test data. That works ok, but that's too hard to understand what's expected from the test not looking at those SQL files. It also added a big chunk of boilerplate at the beginning of each test file for runnig the SQL. In every file it's the same code, changing only what file to use. So they decided to look for some better way of doing that.

Searching for it they found a new module called RedFactory. It's speciffic for Red and uses factories to make it easier to write and read tests written for code that uses Red. The idea about factories is to have a easy way of adding data to your test DB with default values making that easy to populate the test DB at the same file as the test and setting speccific values only for what is needed on the test.

The first thing to be done to use factories is to create the factories themselves. so, for testing the code created here first we would need to create a factory like this one:

use Child;
use Gift;

factory "child", :model(Child), {
  .name    = "Aline";
  .country = "Brazil";
}

factory "gift", :model(Gift), {
  .name = "a gift";
}

That creates 2 factories, one for Child model and another one for Gift model called child and gift respectively. A factory doesn't need to have the same name as its model, but the first factory for a model usualy does. Other factories for that model usualy get more specific names and has more specialised data;

child factory sets default values for 2 columns (name and country) while gift sets only one (name). So lets see how to use that.

RedFactory's factories will use any Red's DB connection you set, so, if you do:

use Factories; # your factories module

my $*RED-DB = database "Pg", :host<some_host>;

my $child = factory-create "child";

That will create a new Child entry on your Pg database. That row will contain:

id name country
?? Aline Brazil

(id will be the next value in the sequence)

And $child will have the object created by Red.

But you usualy don't want to mess with your DB while testing. For helping with that, RedFactory has a helper for running that on a thrown-away DB. So, you could do that instead:

use Factories; # your factories module

my $*RED-DB = factory-db;

my $child = factory-create "child";

That will work exactly as the other snippet, but using a SQLite database in memory. Another way of doing that is using factory-run that will receive a block that will use the in memoty SQLite DB and will receive the RedFactory object, where you can call its methods instead of using the factories functions, for example:

use Factories; # your factories module

factory-run {
  my $child = .create: "child";
}

And it will do exactly the same as the previous snippet.

Ok, that's cool. But what about testing? Let's do that! The elves' code has a function to return the the number of children from a specific country (&children-on-country), so they started writing the test like this:

use Test;
use Factories;      # your factories module
use Child::Helper;  # imports &children-on-country

factory-run {
  is children-on-country("UK"), 0;
  
  .create: "child", :country<UK>;
  is children-on-country("UK"), 1;
    
  .create: 9, "child", :country<UK>;
  is children-on-country("UK"), 10;
}

This is using .create on child factory passing a country value to be used different from default. it also uses .create passing an UInt as first param, that indicates .create to create as many rows as the number passed, and returns a list of those created objects.

But there is a "problem" with that. All created children will have the same name. We can do a small change on that factory to prevent that.

my @children-names = <Fernanda Sophia Eduardo Rafael Maria Lulu>;
my @countries      = <Brazil England Scotland>;

factory "child", :model(Child), {
  .name    = { "{ @children-names.pick } { .counter-by-model }" };
  .country = { @countries.pick };
}

You can pass a block to to have it generate dynamic data for the column. That block will receive a Factory objects that over other things has a method that will return an incremented UInt every time it's called (by model) (.counter-by-model). country is also changed, now it will return a random country for each raw created.

So, that made the elves' tests much simpler to grok.

For more information about RedFactory, please look at https://github.com/FCO/RedFactory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment