Run Integration tests with Rell and TypeScript
Integration testing is crucial for ensuring that the frontend works seamlessly with the backend. In this guide, we'll set up a test harness in TypeScript to perform integration tests on our Rell code. We'll also start a Rell test node and perform queries and transactions.
Verify Your Environment
Ensure you have the following prerequisites set up:
- Docker: Docker should be running, and you should have the necessary permissions to start containers.
- Node.js & npm: Verify that Node.js and npm are installed and configured correctly.
- TypeScript: Ensure TypeScript is installed globally or as part of your project dependencies.
Check Your Project Setup
Make sure your project structure is correct:
- Project Structure: Ensure
src/main.rell
andsrc/main.test.ts
are correctly placed. - Package.json: Verify that your
package.json
includes the necessary scripts and dependencies.
Project setup
Let's set up the project:
chr create-rell-dapp rell-it
cd rell-it
npm init --yes
npm install postchain-client
npm install typescript jest @types/jest ts-jest testcontainers @testcontainers/postgresql --save-dev
npx tsc --init
npx ts-jest config:init
We will use the Jest test framework and Testcontainers for the test harness and postchain-client to communicate with Rell.
Integration test with Jest and Testcontainers
Create the test file src/main.test.ts
to define your integration tests:
import { cwd } from "process";
import { IClient, createClient } from "postchain-client";
import { GenericContainer, Network, StartedNetwork, StartedTestContainer, Wait } from "testcontainers";
import { PostgreSqlContainer, StartedPostgreSqlContainer } from "@testcontainers/postgresql";
// Define the test suite
describe("Rell Integration Tests", () => {
let network: StartedNetwork;
let postgres: StartedPostgreSqlContainer;
let container: StartedTestContainer;
let client: IClient;
// Set up PostgreSQL container and Chromia node container
beforeAll(async () => {
// Start a new network for containers
network = await new Network().start();
// Start a PostgreSQL container
postgres = await new PostgreSqlContainer("postgres:14.9-alpine3.18")
.withNetwork(network)
.withExposedPorts(5432)
.withDatabase("postchain")
.withPassword("postchain")
.withUsername("postchain")
.withNetworkAliases("postgres")
.start();
// Start a Chromia node container
container = await new GenericContainer("registry.gitlab.com/chromaway/core-tools/chromia-cli/chr:latest")
.withNetwork(network)
.withCopyDirectoriesToContainer([{ source: cwd(), target: "/usr/app" }])
.withExposedPorts(7740)
.withEnvironment({
CHR_DB_URL: "jdbc:postgresql://postgres/postchain",
})
.withCommand(["chr", "node", "start", "--wipe"])
.withWaitStrategy(Wait.forLogMessage("Blockchain has been started"))
.withStartupTimeout(60000)
.start();
// Create a Postchain client for communication
client = await createClient({
blockchainIid: 0, // Instance Identifier (Iid) of the targeted blockchain
nodeUrlPool: "http://localhost:" + container.getMappedPort(7740),
});
}, 30000);
// Stop containers after all tests are complete
afterAll(async () => {
await container.stop();
await postgres.stop();
await network.stop();
});
// Integration tests
it("Can update and query dapp", async () => {
// Verify initial query result
expect(await client.query("hello_world")).toBe("Hello World!");
// Send a transaction to update the dapp
expect((await client.sendTransaction({ name: "set_name", args: ["Joe"] })).statusCode).toBe(200);
// Verify the updated query result
expect(await client.query("hello_world")).toBe("Hello Joe!");
});
});
Explanation
- Docker network: A network is created to connect the PostgreSQL and Chromia node containers for seamless communication.
- PostgreSQL container: This container hosts the database for the Rell application, initialized with default credentials.
- Chromia node container: The Chromia CLI is used to start a test node, copy project files, and configure it with
the database URL. The command
chr node start --wipe
initializes the test node. - Client configuration: The
postchain-client
is set up to communicate with the Chromia node using the mapped port.
Initializing containers in beforeAll
is generally best. However, if restarting the Chromia node for each test case is
necessary, use beforeEach
. The PostgreSQL container can persist between tests, so beforeAll
is sufficient.
We finalize the test startup with:
client = await createClient({
blockchainIid: 0,
nodeUrlPool: "http://localhost:" + container.getMappedPort(7740),
});
This configures the nodeUrlPool
to point to port 7740, which is mapped internally to some other port and ensured by
the getMappedPort
call.
We can now add the actual tests as follows:
// Perform integration tests
it("Can update and query dapp", async () => {
// Verify initial query result
expect(await client.query("hello_world")).toBe("Hello World!");
// Send a transaction to update the dapp
expect((await client.sendTransaction({ name: "set_name", args: ["Joe"] })).statusCode).toBe(200);
// Verify the updated query result
expect(await client.query("hello_world")).toBe("Hello Joe!");
});
This test does a query and a transaction towards the node and verifies the results.
Test and debug your setup
-
Run tests: Use the command
npx jest
to run your tests. Ensure Jest is properly installed and configured. -
Test output: Examine the test output for any errors or failures. Ensure your Rell queries and operations are correct.
-
Debugging:
-
Container logs: Check the logs of your Postgres and Chromia containers for any errors.
-
Network configuration: Ensure the containers are correctly networked. They should be able to communicate with each other.
-
Database initialization: Ensure your database is initialized with the correct schema and data for testing.
-
Run the tests
You can run the tests using Jest:
npx jest
PASS src/main.test.ts (11.855 s)
Rell integration tests
✓ Can update and query dapp (1659 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 11.906 s
Ran all test suites.
This command will execute integration tests by interacting with the Rell node and verifying queries and transactions.
Continuous Integration (CI)
Ensure Docker is configured correctly for CI environments to allow Testcontainers to run:
- GitLab CI
- Bitbucket pipelines
# DinD service is required for Testcontainers
services:
- name: docker:dind
# Explicitly disable TLS to avoid Docker startup interruptions
command: ["--tls=false"]
variables:
# Instruct Testcontainers to use the daemon of DinD. Use port 2375 for non-TLS connections.
DOCKER_HOST: "tcp://docker:2375"
# Instruct Docker not to start over TLS.
DOCKER_TLS_CERTDIR: ""
# Improve performance with overlayfs.
DOCKER_DRIVER: overlay2
image: node:16 # Specify a Node.js image
pipelines:
default:
- step:
name: Run Tests
script:
- export TESTCONTAINERS_RYUK_DISABLED=true # Disable Ryuk if necessary
- npx jest
services:
- docker
definitions:
services:
docker:
memory: 2048 # Allocate memory for the Docker service
Troubleshooting tips
- Container startup: If containers fail to start, verify that the images are available and up-to-date.
- Network issues: Ensure Docker's network mode allows containers to communicate properly.
- Timeouts: If tests timeout, consider increasing the timeout value or ensuring that the Chromia node starts quickly enough.