Skip to content

Testing Guide

Testing practices for ION Flow - How to write and run tests.


ION Flow uses Go’s built-in testing framework. All core actions require tests, and comprehensive testing is encouraged for services and controllers.


Terminal window
# From project root
go test ./...
# With verbose output
go test -v ./...
# With coverage
go test -cover ./...
# Coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
Terminal window
# Core tests
cd core && go test ./...
# Backend tests
cd backend && go test ./...
# Package tests
cd packages/channel && go test ./...
Terminal window
go test -run TestFunctionName ./path/to/package

package mypackage
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestFunctionName_Scenario(t *testing.T) {
// Arrange
input := "test input"
expected := "expected output"
// Act
result := FunctionName(input)
// Assert
assert.Equal(t, expected, result)
}
func TestExecute(t *testing.T) {
tests := []struct {
name string
input interface{}
expected interface{}
wantErr bool
}{
{
name: "valid input",
input: map[string]any{"key": "value"},
expected: map[string]any{"result": "value"},
wantErr: false,
},
{
name: "invalid input",
input: nil,
expected: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := Execute(tt.input)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.expected, result)
})
}
}

package condition
import (
"testing"
"gitlab.com/altacrest/flow_binaries/core/models"
"gitlab.com/altacrest/flow_binaries/core/models/status"
)
func TestExecute_TrueCondition(t *testing.T) {
node := models.Node{
ID: "test_node",
Type: "condition",
Data: []interface{}{
map[string]interface{}{
"field": "active",
"operator": "is_equal",
"value": "true",
},
},
}
queue := models.Queue{
Data: map[string]any{"test": "data"},
}
outputs, message, sts := Execute(node, queue)
if sts != status.Success {
t.Errorf("Expected success, got %s: %s", sts, message)
}
if len(outputs) == 0 {
t.Error("Expected outputs, got none")
}
if *outputs[0].TargetHandle != "then" {
t.Errorf("Expected 'then' output, got '%s'", *outputs[0].TargetHandle)
}
}
func TestExecute_FalseCondition(t *testing.T) {
// Similar test for false branch
}
func TestExecute_InvalidOperator(t *testing.T) {
// Test error handling
}

package services
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetCompanyFlow(t *testing.T) {
// Setup test company
company := &models.Company{
ID: 1,
Schema: "test_schema",
}
// Create test flow
flow := &models.Flow{
ID: 1,
Name: "Test Flow",
}
// Test
result, err := GetCompanyFlow(company, 1)
require.NoError(t, err)
assert.Equal(t, flow.Name, result.Name)
}

package controllers
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFindCompanyFlow(t *testing.T) {
// Create request
req, err := http.NewRequest("GET", "/api/1.0/tenants/1/flows/1", nil)
if err != nil {
t.Fatal(err)
}
// Create response recorder
rr := httptest.NewRecorder()
// Create handler
handler := http.HandlerFunc(FindCompanyFlow)
// Execute
handler.ServeHTTP(rr, req)
// Assert
assert.Equal(t, http.StatusOK, rr.Code)
}
func TestNewCompanyFlow(t *testing.T) {
body := map[string]any{
"name": "New Flow",
"flow": map[string]any{
"nodes": []any{},
"edges": []any{},
},
}
jsonBody, _ := json.Marshal(body)
req, _ := http.NewRequest("POST", "/api/1.0/tenants/1/flows", bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(NewCompanyFlow)
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusCreated, rr.Code)
}

package helpers
import (
"testing"
)
func TestParseStringWithDefault(t *testing.T) {
tests := []struct {
input string
def string
expected string
}{
{"value", "default", "value"},
{"", "default", "default"},
{" ", "default", "default"},
}
for _, tt := range tests {
result := ParseStringWithDefault(tt.input, tt.def)
if result != tt.expected {
t.Errorf("ParseStringWithDefault(%q, %q) = %q; want %q",
tt.input, tt.def, result, tt.expected)
}
}
}

import "github.com/stretchr/testify/assert"
// Equality
assert.Equal(t, expected, actual)
assert.NotEqual(t, unexpected, actual)
// Nil checks
assert.Nil(t, value)
assert.NotNil(t, value)
// Error checks
assert.Error(t, err)
assert.NoError(t, err)
// Boolean
assert.True(t, condition)
assert.False(t, condition)
// Contains
assert.Contains(t, slice, element)

Same as assert but fails immediately:

import "github.com/stretchr/testify/require"
require.NoError(t, err) // Stops test if error

For complex dependencies, use interfaces and mocks:

// Interface
type FlowService interface {
GetFlow(id string) (*models.Flow, error)
}
// Mock
type MockFlowService struct {
GetFlowFunc func(id string) (*models.Flow, error)
}
func (m *MockFlowService) GetFlow(id string) (*models.Flow, error) {
return m.GetFlowFunc(id)
}
// Usage in test
func TestController(t *testing.T) {
mock := &MockFlowService{
GetFlowFunc: func(id string) (*models.Flow, error) {
return &models.Flow{ID: 1, Name: "Test"}, nil
},
}
// Use mock in test
}

ModuleMinimum Coverage
Core ActionsRequired
ServicesRecommended
ControllersOptional
HelpersRecommended