Skip to main content

Smartest vs Pytest

Smartest brings pytest-style fixture injection to Ruby without copying Python's decorator syntax.

In pytest, a test asks for a fixture by naming a function parameter. In Smartest, a test asks for a fixture by naming a required Ruby keyword argument:

pytest
import pytest

@pytest.fixture
def user():
return User(name="Alice")

def test_user(user):
assert user.name == "Alice"
Smartest
class AppFixture < Smartest::Fixture
fixture :user do
User.new(name: "Alice")
end
end

around_suite do |suite|
use_fixture AppFixture
suite.run
end

test("user") do |user:|
expect(user.name).to eq("Alice")
end

fixture and suite_fixture are class macros inside Smartest::Fixture subclasses. They are not top-level APIs in test files.

Fixture Dependencies

Pytest fixtures can depend on other fixtures by naming them as function parameters:

pytest
@pytest.fixture
def client(server):
return Client(base_url=server.url)

Smartest uses required keyword arguments for the same idea:

Smartest
class WebFixture < Smartest::Fixture
suite_fixture :server do
TestServer.start
end

fixture :client do |server:|
Client.new(base_url: server.url)
end
end

When a test requests client, Smartest resolves server first. The dependency graph stays visible in fixture signatures instead of being hidden in test body calls.

Teardown

Pytest often uses yield fixtures or finalizers for teardown:

pytest
@pytest.fixture
def server():
server = TestServer.start()
yield server
server.stop()

Smartest keeps teardown near the resource setup with on_teardown:

Smartest
class WebFixture < Smartest::Fixture
fixture :server do
server = TestServer.start
on_teardown { server.stop }
server
end
end

Teardown runs even if a later setup step or the test body fails. Regular fixture teardown runs after the test. suite_fixture teardown runs after the suite.

Optional Autouse-Style Setup

Smartest normally keeps method stubs in explicitly requested fixtures, just like the fixture examples above. When Pytest would use an autouse fixture because broad setup should apply without appearing in every test signature, Smartest can use a hook-scoped method stub:

pytest
@pytest.fixture(autouse=True)
def stub_payment_gateway(monkeypatch):
monkeypatch.setattr(
PaymentGateway,
"charge",
lambda self, *args, **kwargs: "approved",
)
Smartest
around_test do |test|
simple_stub_any_instance_of(PaymentGateway, :charge) { :approved }
test.run
end

Neither test signature needs a stub-only dependency. Pytest undoes the monkeypatch when the autouse fixture finishes; Smartest resets the method stub when the hook exits. Use around_suite instead when one stub should cover the whole Smartest suite.

Keep scenario-specific stubs and all stubs that need fixture data in regular Smartest fixtures. Use this hook form only when intentionally implicit, autouse-style setup is clearer than a fixture keyword.

Concept Map

Pytest conceptSmartest equivalentNotes
@pytest.fixturefixture inside class < Smartest::FixtureSmartest fixtures are grouped in Ruby classes.
Test function parameterRequired keyword argument`test("name") do
Fixture parameterFixture block keyword argument`fixture :client do
scope="session"suite_fixtureCreated lazily and reused for the suite.
autouse=True setup with monkeypatchOptional around_test or around_suite method stubIntentionally broad setup applies without a stub-only test keyword.
yield teardown or finalizeron_teardownRegister teardown immediately after acquiring the resource.
conftest.py discoveryrequire fixture files and use_fixtureSmartest registers fixture classes explicitly.

How This Differs From Ruby Fixtures

In Ruby projects, "fixtures" often means Rails database records, FactoryBot factories, or helper methods hidden behind a test framework DSL. Smartest uses a narrower meaning:

  • a fixture is a named setup value
  • tests declare fixture usage in their keyword arguments
  • fixtures declare dependencies in their keyword arguments
  • teardown belongs next to the resource setup

This makes Smartest useful when you want pytest-style dependency injection for Ruby tests while keeping fixture ownership explicit.