-
Notifications
You must be signed in to change notification settings - Fork 0
Testing
This page covers the SDK's own test suite and verification commands (for contributors), and the two patterns that make testing your app's integration deterministic: direct-data mode and a capturing log sink.
Run these before opening a PR. Each maps to a gate the Quality Checks workflow enforces.
| Command | What it enforces |
|---|---|
bundle exec rspec |
The full RSpec suite on the current Ruby, with the coverage gates: an 85% global line floor, and ≥ 95% line + branch coverage on the critical algorithm group (Hashing, Bucketing, Rules). Coverage is single-sourced in spec/spec_helper.rb. |
bundle exec rake |
The default task: RSpec plus RuboCop. |
bundle exec rbs -r net-http -r uri -r json -I sig validate |
RBS signature validity (CRuby-only). |
bundle exec steep check |
Static type check of sig/ against lib/ (CRuby-only). |
DISABLE_COVERAGE=1 bundle exec rspec spec/cross_sdk |
The release-blocking Cross-SDK parity (MurmurHash3) gate — 100% of the vendored vectors must pass. DISABLE_COVERAGE=1 matches CI: this narrow subset opts out of the global line gate (coverage is enforced by the full-suite run). |
bundle exec rspec spec/integration/full_chain_spec.rb |
The release-blocking Full-chain release gate — the create→decide→track→flush loop, exact wire bytes, and zero raw-secret leakage at TRACE. |
cd demo/rails && bundle exec ruby script/fork_smoke.rb |
The release-blocking Puma-cluster fork smoke — boots a real Puma cluster (workers 2, preload_app!) and asserts events from ≥ 2 distinct forked worker PIDs reach the track endpoint. Runs offline against a local stub. |
The suite runs on the CRuby 3.1–3.4 + JRuby matrix; coverage is enforced on CRuby only (the JRuby leg runs the suite without the gate). Do not change the coverage configuration — it is single-sourced in spec/spec_helper.rb.
| Directory | What lives there |
|---|---|
spec/unit/ |
Per-class unit specs (mock collaborators, isolated logic). |
spec/integration/ |
End-to-end specs: full_chain_spec.rb (the release-blocking create→decide→track→flush loop), runtime_recipes_spec.rb (the runtime-lifecycle recipes the quickstarts are transcribed from), fork_safety_spec.rb, factory_wiring_spec.rb. |
spec/cross_sdk/ |
The cross-SDK MurmurHash3 parity vectors (the byte-identical bucketing proof). |
spec/docs/ |
Docs-snippet smoke specs — run the documented code samples against the real gem so documentation never drifts. |
spec/fixtures/ |
Vendored config fixtures (test-config.json). |
The biggest source of test flakiness is the asynchronous config fetch. Avoid it entirely by building the client in direct-data mode with a known config fixture, so decisions are available without the network:
require "convert_sdk"
require "json"
config = JSON.parse(File.read("spec/fixtures/test-config.json"))
sdk = ConvertSdk.create(data: config)
context = sdk.create_context("test-visitor")
variation = context.run_experience("homepage-test")
# assert on variation&.keyWith data: set, the SDK installs its config without an HTTP call and fires ready synchronously. Use a stable explicit visitor id (e.g. "test-visitor") so bucketing is reproducible run to run — the same id always maps to the same variation for a given experience.
Disable outbound tracking so tests emit no network events. Bucketing, rule evaluation, and sticky persistence still work with tracking off — only delivery is silenced:
sdk = ConvertSdk.create(data: config, tracking: false)For a single call, use the per-call override: context.run_experience("key", { enable_tracking: false }). See Tracking Control.
Any object that duck-types debug/info/warn/error is a valid log sink. A tiny in-memory capturing sink lets you assert on the lines the SDK emits — and is the pattern the SDK's own full-lifecycle TRACE gate uses to prove no raw secret reaches a sink:
class CapturingSink
attr_reader :entries
def initialize
@entries = []
end
%i[debug info warn error].each do |level|
define_method(level) { |message| @entries << [level, message.to_s] }
end
def messages = @entries.map(&:last)
end
sink = CapturingSink.new
sdk = ConvertSdk.create(
data: config,
log_level: ConvertSdk::LogLevel::TRACE,
sink: sink
)
# ... exercise the SDK ...
expect(sink.messages).to include(a_string_matching(/installed direct data config/))Passing sink: at create time captures the construction-time lines too. See Initialization and the TRACE logging section.
- Use explicit visitor ids — never rely on a random id in tests; an explicit id makes bucketing assertions stable.
- Prefer fixtures over live keys — direct-data mode with a checked-in config fixture keeps tests hermetic and fast.
- Build a fresh client per test (or per group) so visitor and queue state do not leak between tests.
-
Initialization — direct-data mode and the
sink:seam - Tracking Control — disabling tracking
- Fork Safety & Runtime Recipes — TRACE logging and the fork smoke
Copyrights © 2026 All Rights Reserved by Convert Insights, Inc.
Getting Started
Ruby SDK
- Quickstart
- Installation
- Initialization
- Configuration
- Return Types & Sentinels
- Code Examples
- Fork Safety & Runtime Recipes
- Tracking Control
Core Concepts
- Experiences & Variations
- Feature Flags
- Bucketing Algorithm
- Rule Evaluation
- Segments
- Data Management
- Event System
- API Communication
How-To Guides
- Running Experiences
- Running Features
- Tracking Conversions
- Visitor Context
- Persistent DataStore
- Troubleshooting
Contributing