Testing Raku Applications with Cro, Red, and RedFactory

Introduction Automated testing is crucial in modern software development. It helps ensure quality and prevent regressions by quickly verifying if the system continues working after code changes. Unlike manual tests, automated tests can be run repeatedly, ensuring consistency and speeding up validation. Moreover, they improve reliability by confirming that software inputs and outputs remain correct and aligned with requirements. In this article, we’ll explore how to test applications written in Raku using three main tools: Cro, Red, and RedFactory. Cro is a set of libraries for building reactive services (such as web applications and HTTP APIs) in Raku. Red is an Object-Relational Mapper (ORM) for Raku. Red allows you to interact with relational databases using Raku objects instead of writing raw SQL, bringing several advantages like type safety, consistency, and easier schema evolution. RedFactory is a helper library designed to work with Red, making it easy to generate consistent test data. Combining Cro, Red, and RedFactory, we can create web applications in Raku backed by a database and write efficient automated tests for them. Let’s walk step-by-step through setting up a simple project with these tools, defining database models, using factories to generate test data, and writing automated tests—including full HTTP integration tests using Cro::HTTP::Test. Setting Up the Environment To set up a basic project using Cro, Red, and RedFactory: Install Raku and dependencies: Ensure you have Raku installed. Then install Cro, Red, and RedFactory using zef: zef install cro Red RedFactory Create a new Cro project: Cro provides a CLI tool to generate project skeletons: cro stub http MyApp myapp This creates a project under myapp/ with initial files and structure. Add Red to the project: Import Red into your main script or modules: use Red:api; Configure the database connection: red-defaults "SQLite", database => "myapp.db"; Install and configure RedFactory: You already installed RedFactory in step 1. Create a file like lib/Factories.rakumod where you will define factories for your models. Now your environment is ready: a basic Cro project running, Red handling database interaction, and RedFactory set up to create test data easily. Creating Models with Red Models represent database tables as Raku classes. Let’s define a simple model for children, storing their name and country: use Red:api; model Child { has UInt $.id is serial; has Str $.name is column; has Str $.country is column; } is serial marks the ID as an auto-increment primary key. is column indicates the attribute maps to a database column. Before using the model, ensure the table exists: Child.^create-table: :unless-exists; Now you can create records: Child.^create: name => "Alice", country => "Brazil"; Or query them: for Child.^all -> $child { say $child.name, " - ", $child.country; } Red abstracts away SQL, making database operations much simpler and safer. Creating Factories with RedFactory Factories allow us to create consistent test data easily. Define them like this in Factories.rakumod: use RedFactory; use Child; factory "child", :model(Child), { .name = "Test"; .country = "Unknown"; } Now you can create test records in one line: my $child = factory-create "child"; Or override attributes: factory-create "child", :name; You can even create multiple records: factory-create 5, "child"; Factories make tests cleaner and easier to maintain. Introduction to Cro::HTTP::Test Testing HTTP routes without running a real server is made easy by Cro::HTTP::Test. This module allows you to: Execute your routes in-memory. Send simulated HTTP requests. Check responses declaratively. Example: use Test; use Cro::HTTP::Test; use MyApp::Routes; test-service routes(), { test get("/hello/World"), status => 200, content-type => 'text/plain', body => "Hello, World!"; }; done-testing; test-service wraps your routes for testing. test sends a request and checks expectations like status code, content type, and body. No need to bind to ports or manage real HTTP connections—everything is fast and isolated. Writing Tests Let’s use RedFactory and Cro::HTTP::Test together. Imagine we have this API route: get -> 'children', 'count', $country { my $count = Child.^all.grep(*.country eq $country).elems; content 'application/json', { :count($count) }; } We can test it: use Test; use Cro::HTTP::Test; use Factories; use MyApp::Routes; factory-run { Child.^create-table: :unless-exists; .create: 10, "child", :country; test-service routes(), { test get("/children/count/UK"), status => 200, content-type => 'application/json',

Apr 27, 2025 - 04:28
 0
Testing Raku Applications with Cro, Red, and RedFactory

Introduction

Automated testing is crucial in modern software development. It helps ensure quality and prevent regressions by quickly verifying if the system continues working after code changes. Unlike manual tests, automated tests can be run repeatedly, ensuring consistency and speeding up validation. Moreover, they improve reliability by confirming that software inputs and outputs remain correct and aligned with requirements.

In this article, we’ll explore how to test applications written in Raku using three main tools: Cro, Red, and RedFactory.

  • Cro is a set of libraries for building reactive services (such as web applications and HTTP APIs) in Raku.
  • Red is an Object-Relational Mapper (ORM) for Raku. Red allows you to interact with relational databases using Raku objects instead of writing raw SQL, bringing several advantages like type safety, consistency, and easier schema evolution.
  • RedFactory is a helper library designed to work with Red, making it easy to generate consistent test data.

Combining Cro, Red, and RedFactory, we can create web applications in Raku backed by a database and write efficient automated tests for them. Let’s walk step-by-step through setting up a simple project with these tools, defining database models, using factories to generate test data, and writing automated tests—including full HTTP integration tests using Cro::HTTP::Test.

Setting Up the Environment

To set up a basic project using Cro, Red, and RedFactory:

  1. Install Raku and dependencies: Ensure you have Raku installed. Then install Cro, Red, and RedFactory using zef:
zef install cro Red RedFactory
  1. Create a new Cro project: Cro provides a CLI tool to generate project skeletons:
cro stub http MyApp myapp

This creates a project under myapp/ with initial files and structure.

  1. Add Red to the project: Import Red into your main script or modules:
use Red:api<2>;

Configure the database connection:

red-defaults "SQLite", database => "myapp.db";
  1. Install and configure RedFactory: You already installed RedFactory in step 1. Create a file like lib/Factories.rakumod where you will define factories for your models.

Now your environment is ready: a basic Cro project running, Red handling database interaction, and RedFactory set up to create test data easily.

Creating Models with Red

Models represent database tables as Raku classes. Let’s define a simple model for children, storing their name and country:

use Red:api<2>;

model Child {
    has UInt $.id       is serial;
    has Str  $.name     is column;
    has Str  $.country  is column;
}
  • is serial marks the ID as an auto-increment primary key.
  • is column indicates the attribute maps to a database column.

Before using the model, ensure the table exists:

Child.^create-table: :unless-exists;

Now you can create records:

Child.^create: name => "Alice", country => "Brazil";

Or query them:

for Child.^all -> $child {
    say $child.name, " - ", $child.country;
}

Red abstracts away SQL, making database operations much simpler and safer.

Creating Factories with RedFactory

Factories allow us to create consistent test data easily. Define them like this in Factories.rakumod:

use RedFactory;
use Child;

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

Now you can create test records in one line:

my $child = factory-create "child";

Or override attributes:

factory-create "child", :name;

You can even create multiple records:

factory-create 5, "child";

Factories make tests cleaner and easier to maintain.

Introduction to Cro::HTTP::Test

Testing HTTP routes without running a real server is made easy by Cro::HTTP::Test.

This module allows you to:

  • Execute your routes in-memory.
  • Send simulated HTTP requests.
  • Check responses declaratively.

Example:

use Test;
use Cro::HTTP::Test;
use MyApp::Routes;

test-service routes(), {
    test get("/hello/World"),
        status       => 200,
        content-type => 'text/plain',
        body         => "Hello, World!";
};
done-testing;

test-service wraps your routes for testing.
test sends a request and checks expectations like status code, content type, and body.

No need to bind to ports or manage real HTTP connections—everything is fast and isolated.

Writing Tests

Let’s use RedFactory and Cro::HTTP::Test together.

Imagine we have this API route:

get -> 'children', 'count', $country {
    my $count = Child.^all.grep(*.country eq $country).elems;
    content 'application/json', { :count($count) };
}

We can test it:

use Test;
use Cro::HTTP::Test;
use Factories;
use MyApp::Routes;

factory-run {
    Child.^create-table: :unless-exists;

    .create: 10, "child", :country;

    test-service routes(), {
        test get("/children/count/UK"),
            status       => 200,
            content-type => 'application/json',
            json         => { :count(10) };
    };
}
done-testing;
  • factory-run sets up a fresh SQLite in-memory database.
  • We create 10 children from the UK.
  • Then we test that the /children/count/UK route correctly returns 10.

This shows how Cro, Red, and RedFactory work together cleanly.

Full Integration Example

Suppose our API has:

  • GET /children — list all children.
  • POST /children — create a new child.

We can test the full flow:

use Test;
use Cro::HTTP::Test;
use Factories;
use MyApp::Routes;

factory-run {
    Child.^create-table: :unless-exists;

    test-service routes(), {
        test get("/children"),
            status => 200,
            content-type => 'application/json',
            json => [];
    };

    my %new-data = ( name => "John", country => "Portugal" );
    test-service routes(), {
        test post("/children", json => %new-data),
            status => 201,
            content-type => 'application/json',
            json => { :name("John"), :country("Portugal"), *%_ };
    };

    test-service routes(), {
        test get("/children"),
            status => 200,
            json => [ { :name("John"), :country("Portugal"), *%_ } ];
    };
}
done-testing;

We:

  • Start with an empty list.
  • Add a child via POST.
  • Confirm the list now contains the new child.

Simple, clean, and fully automated.

Best Practices

  • Isolated Database for Tests: Always use a test database, ideally in-memory. factory-run with RedFactory makes this easy.
  • Use Factories: Define clear factories for your models to make tests readable.
  • Separate Unit and Integration Tests: Keep logic-only tests separate from HTTP integration tests.
  • Check Only Necessary Parts: Use partial JSON assertions with *%_ to avoid fragile tests.
  • Organize Code: Keep routes modular and tests structured.

Good practices help you keep tests maintainable, readable, and fast.

Conclusion

In this article, we showed how to test Raku applications using Cro, Red, and RedFactory:

  • Set up a project environment.
  • Define database models.
  • Create factories to generate test data.
  • Use Cro::HTTP::Test to simulate HTTP requests and verify responses.

The Red ORM helps you avoid boilerplate SQL and interact with the database elegantly.
RedFactory makes test data creation effortless.
Cro::HTTP::Test allows fast, isolated HTTP testing.

If you’re building web applications in Raku, combining these tools will dramatically boost your productivity and confidence in your code quality.

Give Red a try in your next Raku project—you’ll love how much easier database interactions and testing become!