Testing
Testing Reference
Section titled “Testing Reference”Complete reference for writing and running tests in Jac.
Test Syntax
Section titled “Test Syntax”Basic Test
Section titled “Basic Test”test "my feature" { # Test body assert condition;}Test with Setup
Section titled “Test with Setup”obj MyObject { has data: str;
def process() -> str { return self.data; }}
test "object processing" { # Setup my_obj = MyObject(data="test");
# Test result = my_obj.process();
# Assert assert result == "test";}Assertions
Section titled “Assertions”Basic Assert
Section titled “Basic Assert”test "basic assert" { assert condition; assert condition, "Error message";}Equality
Section titled “Equality”test "equality checks" { assert a == b; # Equal assert a != b; # Not equal assert a is b; # Same object assert a is not b; # Different objects}Comparisons
Section titled “Comparisons”test "comparisons" { assert a > b; # Greater than assert a >= b; # Greater or equal assert a < b; # Less than assert a <= b; # Less or equal}Boolean
Section titled “Boolean”test "boolean values" { assert True; assert not False; assert bool(value);}Membership
Section titled “Membership”test "membership" { assert item in collection; assert item not in collection; assert key in dictionary;}Type Checking
Section titled “Type Checking”test "type checking" { assert isinstance(obj, MyClass); assert type(obj) == MyClass;}None Checking
Section titled “None Checking”test "none checking" { assert value is None; assert value is not None;}With Messages
Section titled “With Messages”test "assertions with messages" { assert result > 0, f"Expected positive, got {result}"; assert len(items) == 3, "Should have 3 items";}CLI Commands
Section titled “CLI Commands”Running Tests
Section titled “Running Tests”# Run all tests in a filejac test main.jac
# Run tests in a directoryjac test -d tests/
# Run specific testjac test main.jac -t my_featureCLI Options
Section titled “CLI Options”| Option | Short | Description |
|---|---|---|
--test_name | -t | Run specific test by name |
--filter | -f | Filter tests by pattern |
--xit | -x | Exit on first failure |
--maxfail | -m | Stop after N failures |
--directory | -d | Test directory |
--verbose | -v | Verbose output |
Examples
Section titled “Examples”# Verbose outputjac test main.jac -v
# Stop on first failurejac test main.jac -x
# Filter by patternjac test main.jac -f "user_"
# Max failuresjac test -d tests/ -m 3
# Combinedjac test main.jac -t calculator_add -vTest Output
Section titled “Test Output”Success
Section titled “Success”unittest.case.FunctionTestCase (test_add) ... okunittest.case.FunctionTestCase (test_subtract) ... ok
----------------------------------------------------------------------Ran 2 tests in 0.001s
OKFailure
Section titled “Failure”unittest.case.FunctionTestCase (test_add) ... FAIL
======================================================================FAIL: test_add----------------------------------------------------------------------AssertionError: Expected 5, got 4
----------------------------------------------------------------------Ran 1 test in 0.001s
FAILED (failures=1)Testing Patterns
Section titled “Testing Patterns”Testing Objects
Section titled “Testing Objects”obj Calculator { has value: int = 0;
def add(n: int) -> int { self.value += n; return self.value; }
def reset() -> None { self.value = 0; }}
test "calculator add" { calc = Calculator(); assert calc.add(5) == 5; assert calc.add(3) == 8; assert calc.value == 8;}
test "calculator reset" { calc = Calculator(); calc.add(10); calc.reset(); assert calc.value == 0;}Testing Nodes and Walkers
Section titled “Testing Nodes and Walkers”node Counter { has count: int = 0;}
walker Incrementer { has amount: int = 1;
can start with Root entry { visit [-->]; }
can increment with Counter entry { here.count += self.amount; }}
test "walker increments" { counter = root ++> Counter(); root spawn Incrementer(); assert counter[0].count == 1;}
test "walker custom amount" { counter = root ++> Counter(); root spawn Incrementer(amount=5); assert counter[0].count == 5;}Testing Walker Reports
Section titled “Testing Walker Reports”node Person { has name: str; has age: int;}
walker FindAdults { can check with Root entry { for person in [-->](?:Person) { if person.age >= 18 { report person; } } }}
test "find adults" { root ++> Person(name="Alice", age=30); root ++> Person(name="Bob", age=15); root ++> Person(name="Carol", age=25);
result = root spawn FindAdults();
assert len(result.reports) == 2; names = [p.name for p in result.reports]; assert "Alice" in names; assert "Carol" in names; assert "Bob" not in names;}Testing Graph Structure
Section titled “Testing Graph Structure”node Room { has name: str;}
edge Door {}
test "graph connections" { kitchen = Room(name="Kitchen"); living = Room(name="Living Room"); bedroom = Room(name="Bedroom");
root ++> kitchen; kitchen +>: Door() :+> living; living +>: Door() :+> bedroom;
# Test connections assert len([root -->]) == 1; assert len([kitchen -->]) == 1; assert living in [kitchen ->:Door:->]; assert bedroom in [living ->:Door:->];}Testing Exceptions
Section titled “Testing Exceptions”def divide(a: int, b: int) -> float { if b == 0 { raise ZeroDivisionError("Cannot divide by zero"); } return a / b;}
test "divide normal" { assert divide(10, 2) == 5;}
test "divide by zero" { try { divide(10, 0); assert False, "Should have raised error"; } except ZeroDivisionError { assert True; # Expected }}Project Organization
Section titled “Project Organization”Separate Test Files
Section titled “Separate Test Files”myproject/├── jac.toml├── src/│ ├── models.jac│ └── walkers.jac└── tests/ ├── test_models.jac └── test_walkers.jac# Run all testsjac test -d tests/
# Run specific filejac test tests/test_models.jacTests in Same File
Section titled “Tests in Same File”# models.jac
obj User { has name: str; has email: str;
def is_valid() -> bool { return len(self.name) > 0 and "@" in self.email; }}
# Tests at bottomtest "user valid" { user = User(name="Alice", email="alice@example.com"); assert user.is_valid();}
test "user invalid email" { user = User(name="Alice", email="invalid"); assert not user.is_valid();}Configuration
Section titled “Configuration”jac.toml
Section titled “jac.toml”[test]directory = "tests"verbose = truefail_fast = falsemax_failures = 10JacTestClient
Section titled “JacTestClient”JacTestClient provides an in-process HTTP client for testing Jac API endpoints without starting a real server or opening network ports.
Import
Section titled “Import”from jaclang.runtimelib.testing import JacTestClientCreating a Client
Section titled “Creating a Client”# Create from a .jac fileclient = JacTestClient.from_file("app.jac")
# With a custom base path (useful for temp directories in tests)client = JacTestClient.from_file("app.jac", base_path="/tmp/test")Authentication
Section titled “Authentication”# Register a test userresponse = client.register_user("testuser", "password123")
# Loginresponse = client.login("testuser", "password123")
# Manually set auth tokenclient.set_auth_token("eyJ...")
# Clear authclient.clear_auth()Making Requests
Section titled “Making Requests”# GET requestresponse = client.get("/walker/get_users")
# POST request with JSON bodyresponse = client.post("/walker/create_user", json={"name": "Alice"})
# PUT requestresponse = client.put("/walker/update_user", json={"name": "Bob"})
# Generic requestresponse = client.request("DELETE", "/walker/delete_user", json={"id": "123"})
# With custom headersresponse = client.get("/walker/data", headers={"X-Custom": "value"})TestResponse
Section titled “TestResponse”Responses from JacTestClient are TestResponse objects:
| Property/Method | Type | Description |
|---|---|---|
status_code | int | HTTP status code |
headers | dict | Response headers |
text | str | Raw response body |
json() | dict | Parse body as JSON |
ok | bool | True if status is 2xx |
data | dict | None | Unwrapped data from TransportResponse envelope |
Full Example
Section titled “Full Example”import pytestfrom jaclang.runtimelib.testing import JacTestClient
def test_task_crud(tmp_path): client = JacTestClient.from_file("app.jac", base_path=str(tmp_path))
# Register and authenticate client.register_user("testuser", "password123")
# Create resp = client.post("/walker/CreateTask", json={"title": "My Task"}) assert resp.status_code == 200 assert resp.ok
# Read resp = client.post("/walker/GetTasks") data = resp.json() assert len(data["reports"]) == 1
# Cleanup client.close()HMR Testing
Section titled “HMR Testing”Test hot module replacement behavior:
def test_hmr(tmp_path): client = JacTestClient.from_file("app.jac", base_path=str(tmp_path)) client.register_user("user", "pass")
# Initial state resp = client.post("/walker/get_data") assert resp.ok
# Simulate file change and reload client.reload()
# Verify after reload resp = client.post("/walker/get_data") assert resp.ok
client.close()Best Practices
Section titled “Best Practices”1. Descriptive Names
Section titled “1. Descriptive Names”# Good - use readable descriptionstest "user creation with valid email" { }test "walker visits all connected nodes" { }
# Avoid - vague or cryptic namestest "t1" { }test "thing" { }2. One Focus Per Test
Section titled “2. One Focus Per Test”# Good - focused teststest "add positive numbers" { assert add(2, 3) == 5;}
test "add negative numbers" { assert add(-2, -3) == -5;}
# Avoid - too broadtest "all math operations" { assert add(2, 3) == 5; assert subtract(5, 3) == 2; assert multiply(2, 3) == 6;}3. Isolate Tests
Section titled “3. Isolate Tests”# Good - creates fresh statetest "counter increment" { counter = root ++> Counter(); root spawn Incrementer(); assert counter[0].count == 1;}
# Each test should be independenttest "counter starts at zero" { counter = Counter(); assert counter.count == 0;}4. Test Edge Cases
Section titled “4. Test Edge Cases”test "empty list" { result = process([]); assert result == [];}
test "single item" { result = process([1]); assert len(result) == 1;}
test "large list" { result = process(list(range(1000))); assert len(result) == 1000;}5. Clear Assertions
Section titled “5. Clear Assertions”# Good - clear what failedtest "calculation with message" { result = calculate(input); assert result == expected, f"Expected {expected}, got {result}";}
# Avoid - unclear failurestest "calculation no message" { assert calculate(input) == expected;}