agentsclimarketplace

Ruby testunit

Skill PramodDutta/qaskills/seed-skills/ruby-testunit

QA Skills Directory QA Skills is a curated directory of testing-specific skills for AI coding agents (Claude Code, Cursor, Copilot, etc.).

Install
npx -y skills add PramodDutta/qaskills --skill ruby-testunit

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

Classic xUnit-style Ruby testing with Test::Unit covering assertions, fixtures, test case organization, mocking patterns, and lifecycle hooks for reliable Ruby application testing.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

12.6 KB, ~3.2k tokens by cl100k_base, as published. Nobody here has run it

Ruby Test::Unit Skill

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.

Core Principles

  1. Test behavior through public interfaces -- Verify what the code does from a caller's perspective rather than inspecting internal state.
  2. One assertion focus per test -- Each test method should verify a single logical behavior for precise failure diagnostics.
  3. Arrange-Act-Assert -- Structure every test into setup, execution, and verification phases for readability and consistency.
  4. Isolate tests completely -- Each test must run independently and produce the same result regardless of execution order.
  5. Descriptive test names -- Name tests as test_<method>_<scenario>_<expected> so output reads as a living specification.
  6. Use fixtures for shared state -- Leverage setup and teardown for per-test initialization and cleanup.
  7. Cover edge cases -- Test boundary values, empty inputs, nil handling, and error conditions explicitly.

Project Structure

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

Configuration

Gemfile

group :test do
  gem 'test-unit', '~> 3.6'
  gem 'mocha', '~> 2.1'
  gem 'simplecov', require: false
end

test_helper.rb

require 'simplecov'
SimpleCov.start

require 'test-unit'
require 'mocha/test_unit'

$LOAD_PATH.unshift File.expand_path('../lib', __dir__)

Rakefile

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

Running Tests

# 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/

Basic Test Structure

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

Assertion Methods Reference

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

Mocking with Mocha

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

Stubbing Methods

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

Lifecycle Hooks

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

Data-Driven Tests

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

Custom Assertions

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

Best Practices

  1. Use setup and teardown for consistent state -- Initialize shared objects in setup and release resources in teardown so each test starts fresh.
  2. Prefer specific assertions -- Use assert_equal over assert(a == b) for better error messages and clarity on what failed.
  3. Use data method for parameterized tests -- Test::Unit's data-driven testing keeps multiple test cases organized and produces clear output per data set.
  4. Mock external dependencies -- Use Mocha to stub HTTP clients, databases, and third-party APIs while testing business logic in isolation.
  5. Test exceptions with assert_raise -- Verify both the exception class and message content to ensure errors are meaningful and correct.
  6. Use startup/shutdown for expensive resources -- Share database connections or file handles across all tests in a class to avoid redundant initialization.
  7. Keep test files parallel to source -- Mirror the lib/ directory structure in test/ so developers can quickly locate related tests.
  8. Test edge cases explicitly -- Include nil inputs, empty collections, boundary values, and Unicode strings in your test data.
  9. Run tests in random order -- Configure random seed execution to catch order-dependent test coupling.
  10. Use SimpleCov for coverage tracking -- Measure and enforce coverage thresholds to identify untested code paths.

Anti-Patterns

  1. Testing private methods directly -- Calling private methods via send(:private_method) couples tests to implementation; test through the public API.
  2. Using assert with boolean expressions -- assert(result == expected) gives no useful message on failure; use assert_equal expected, result instead.
  3. Not cleaning up in teardown -- Failing to close file handles, database connections, or temporary files causes resource leaks across test runs.
  4. Over-mocking -- Mocking every dependency makes tests prove nothing about real interactions; mock only I/O and non-deterministic behavior.
  5. Shared mutable class variables -- Modifying @@variables in tests causes order-dependent failures that are notoriously difficult to debug.
  6. Hardcoding absolute paths -- Using platform-specific paths breaks tests on different machines; use File.expand_path and Tempfile.
  7. Large test methods -- Tests exceeding 20 lines usually verify too many things; split into focused test methods with clear names.
  8. Ignoring test output -- Not running with verbose flags means you miss valuable context about which behaviors are covered.
  9. Skipping error path testing -- Only testing the happy path leaves exception handling and edge cases unverified.
  10. Not using assert_nothing_raised -- When testing that code completes without error, use assert_nothing_raised to make the intent explicit.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.