seed-skills/ruby-testunit/SKILL.md
Classic xUnit-style Ruby testing with Test::Unit covering assertions, fixtures, test case organization, mocking patterns, and lifecycle hooks for reliable Ruby application testing.
npx skillsauth add PramodDutta/qaskills Ruby Test::UnitInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an expert Ruby developer specializing in testing with Test::Unit. When the user asks you to write, review, or debug Test::Unit tests, follow these detailed instructions to produce well-structured, reliable test suites that exercise Ruby code thoroughly.
test_<method>_<scenario>_<expected> so output reads as a living specification.setup and teardown for per-test initialization and cleanup.project/
lib/
services/
user_service.rb
payment_service.rb
models/
user.rb
order.rb
utils/
validators.rb
formatters.rb
test/
test_helper.rb
services/
test_user_service.rb
test_payment_service.rb
models/
test_user.rb
test_order.rb
utils/
test_validators.rb
test_formatters.rb
integration/
test_user_payment_flow.rb
fixtures/
sample_users.yml
Gemfile
Rakefile
group :test do
gem 'test-unit', '~> 3.6'
gem 'mocha', '~> 2.1'
gem 'simplecov', require: false
end
require 'simplecov'
SimpleCov.start
require 'test-unit'
require 'mocha/test_unit'
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
require 'rake/testtask'
Rake::TestTask.new(:test) do |t|
t.libs << 'test'
t.libs << 'lib'
t.test_files = FileList['test/**/test_*.rb']
t.verbose = true
end
task default: :test
# Run all tests
ruby -Itest -Ilib test/services/test_user_service.rb
# Run with Rake
rake test
# Run specific test method
ruby -Itest -Ilib test/services/test_user_service.rb --name test_create_user_with_valid_data
# Auto-discovery
testrb test/
require 'test_helper'
require 'services/user_service'
class TestUserService < Test::Unit::TestCase
def setup
@service = UserService.new
@valid_user_data = {
name: 'Alice',
email: '[email protected]',
age: 30
}
end
def teardown
@service = nil
end
def test_create_user_with_valid_data
user = @service.create_user(@valid_user_data)
assert_not_nil user
assert_equal 'Alice', user.name
assert_equal '[email protected]', user.email
end
def test_create_user_without_email_raises_argument_error
invalid_data = { name: 'Bob' }
assert_raise(ArgumentError) do
@service.create_user(invalid_data)
end
end
def test_create_user_with_invalid_email_raises_error
invalid_data = { name: 'Bob', email: 'not-an-email' }
error = assert_raise(ArgumentError) do
@service.create_user(invalid_data)
end
assert_match(/email/, error.message)
end
end
class TestAssertionExamples < Test::Unit::TestCase
def test_equality_assertions
assert_equal 4, 2 + 2
assert_not_equal 5, 2 + 2
assert_in_delta 0.3, 0.1 + 0.2, 0.001
assert_in_epsilon 100, 99, 0.02
end
def test_truth_assertions
assert true
assert_true true
assert_false false
assert_nil nil
assert_not_nil 'value'
end
def test_identity_assertions
a = 'hello'
b = a
assert_same a, b
assert_not_same 'hello', 'hello'
end
def test_type_assertions
assert_instance_of String, 'hello'
assert_kind_of Numeric, 42
assert_kind_of Enumerable, [1, 2, 3]
end
def test_collection_assertions
assert_include [1, 2, 3], 2
assert_not_include [1, 2, 3], 4
assert_empty []
assert_not_empty [1]
end
def test_string_assertions
assert_match(/\d+/, 'hello123')
assert_not_match(/\d+/, 'hello')
end
def test_exception_assertions
assert_raise(ZeroDivisionError) { 1 / 0 }
assert_nothing_raised { 1 + 1 }
end
def test_comparison_assertions
assert_compare 10, '>', 5
assert_compare 5, '<', 10
assert_compare 10, '>=', 10
assert_compare 5, '<=', 5
end
def test_respond_to_assertion
assert_respond_to 'hello', :upcase
assert_respond_to [1, 2], :length
end
end
require 'test_helper'
require 'services/user_service'
class TestUserServiceWithMocks < Test::Unit::TestCase
def setup
@db = mock('database')
@email_client = mock('email_client')
@service = UserService.new(db: @db, email_client: @email_client)
end
def test_get_user_by_id
expected_user = { id: 1, name: 'Alice', email: '[email protected]' }
@db.expects(:find_one).with(id: 1).returns(expected_user)
user = @service.get_user(1)
assert_equal 'Alice', user[:name]
end
def test_get_user_not_found_returns_nil
@db.expects(:find_one).with(id: 999).returns(nil)
user = @service.get_user(999)
assert_nil user
end
def test_create_user_sends_welcome_email
@db.expects(:insert).returns(1)
@email_client.expects(:send).with(
has_entries(to: '[email protected]', subject: 'Welcome')
)
@service.create_user(name: 'Bob', email: '[email protected]')
end
def test_create_user_handles_email_failure_gracefully
@db.expects(:insert).returns(1)
@email_client.expects(:send).raises(RuntimeError, 'SMTP error')
assert_nothing_raised do
@service.create_user(name: 'Bob', email: '[email protected]')
end
end
end
class TestPaymentService < Test::Unit::TestCase
def test_process_payment_calls_gateway
gateway = stub('gateway')
gateway.stubs(:charge).returns({ status: 'success', txn_id: 'abc123' })
service = PaymentService.new(gateway: gateway)
result = service.process_payment(amount: 50.00, card_token: 'tok_123')
assert_equal 'success', result[:status]
end
def test_process_payment_retries_on_timeout
gateway = mock('gateway')
gateway.expects(:charge).times(3).raises(Timeout::Error).then.raises(Timeout::Error).then.returns({ status: 'success' })
service = PaymentService.new(gateway: gateway)
result = service.process_payment(amount: 50.00, card_token: 'tok_123')
assert_equal 'success', result[:status]
end
end
class TestWithLifecycleHooks < Test::Unit::TestCase
class << self
def startup
puts 'Runs once before ALL tests in this class'
@@shared_resource = ExpensiveResource.new
end
def shutdown
puts 'Runs once after ALL tests in this class'
@@shared_resource.close
end
end
def setup
puts 'Runs before EACH test'
@local_state = fresh_state
end
def teardown
puts 'Runs after EACH test'
@local_state = nil
end
def test_example_one
assert_not_nil @@shared_resource
assert_not_nil @local_state
end
def test_example_two
assert_not_nil @@shared_resource
assert_not_nil @local_state
end
private
def fresh_state
{ counter: 0, items: [] }
end
end
class TestEmailValidator < Test::Unit::TestCase
VALID_EMAILS = %w[
[email protected]
[email protected]
[email protected]
[email protected]
].freeze
INVALID_EMAILS = [
'',
'not-an-email',
'@domain.com',
'user@',
'user @domain.com'
].freeze
data(
'simple email' => ['[email protected]', true],
'dotted name' => ['[email protected]', true],
'plus tag' => ['[email protected]', true],
'empty string' => ['', false],
'no at sign' => ['not-an-email', false],
'no local part' => ['@domain.com', false],
)
def test_email_validation(data)
email, expected = data
assert_equal expected, Validators.valid_email?(email),
"Expected valid_email?('#{email}') to return #{expected}"
end
data(
'zero' => [0, false],
'one' => [1, true],
'seventeen' => [17, false],
'eighteen' => [18, true],
'one twenty' => [120, true],
'one twenty one' => [121, false],
'negative' => [-1, false],
)
def test_age_validation(data)
age, expected = data
assert_equal expected, Validators.valid_age?(age)
end
end
module CustomAssertions
def assert_valid_email(email, message = nil)
email_regex = /\A[^@\s]+@[^@\s]+\.[^@\s]+\z/
full_message = build_message(message, "Expected ? to be a valid email", email)
assert_block(full_message) { email_regex.match?(email) }
end
def assert_json_response(response, message = nil)
full_message = build_message(message, "Expected response to be valid JSON with status 200")
assert_block(full_message) do
response.code == '200' && JSON.parse(response.body)
end
end
end
class TestWithCustomAssertions < Test::Unit::TestCase
include CustomAssertions
def test_email_format
assert_valid_email '[email protected]'
end
end
setup and teardown for consistent state -- Initialize shared objects in setup and release resources in teardown so each test starts fresh.assert_equal over assert(a == b) for better error messages and clarity on what failed.data method for parameterized tests -- Test::Unit's data-driven testing keeps multiple test cases organized and produces clear output per data set.assert_raise -- Verify both the exception class and message content to ensure errors are meaningful and correct.startup/shutdown for expensive resources -- Share database connections or file handles across all tests in a class to avoid redundant initialization.lib/ directory structure in test/ so developers can quickly locate related tests.send(:private_method) couples tests to implementation; test through the public API.assert with boolean expressions -- assert(result == expected) gives no useful message on failure; use assert_equal expected, result instead.@@variables in tests causes order-dependent failures that are notoriously difficult to debug.File.expand_path and Tempfile.assert_nothing_raised -- When testing that code completes without error, use assert_nothing_raised to make the intent explicit.development
Generate test data from the schemas you already have. Read OpenAPI, JSON Schema, SQL DDL, or TypeScript models and produce deterministic factories, boundary and negative cases, relational datasets with valid foreign keys, cleanup scripts, and PII-safe synthetic data. Production records never leave the machine.
development
Diagnose flaky test failures from Playwright reports, traces, and rerun history. Classify each failure as product, test, environment, data, or unknown with cited evidence and a proposed fix. Never auto-modifies code without opt-in.
tools
Test LLM, RAG, MCP, and agentic systems end to end. Build golden datasets, run deterministic checks and LLM judges, score retrieval, probe prompt injection, verify tool use, and gate CI on thresholds. Orchestrates DeepEval, Ragas, promptfoo, and Langfuse.
development
Analyze a git diff, map affected risks, select the tests that matter, detect coverage gaps on changed lines, run configurable quality gates, and produce a go/no-go release report with cited evidence. Recommends only; never merges or deploys.