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:
import pytest
@pytest.fixture
def user():
return User(name="Alice")
def test_user(user):
assert user.name == "Alice"
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.fixture
def client(server):
return Client(base_url=server.url)
Smartest uses required keyword arguments for the same idea:
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.fixture
def server():
server = TestServer.start()
yield server
server.stop()
Smartest keeps teardown near the resource setup with on_teardown:
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.fixture(autouse=True)
def stub_payment_gateway(monkeypatch):
monkeypatch.setattr(
PaymentGateway,
"charge",
lambda self, *args, **kwargs: "approved",
)
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 concept | Smartest equivalent | Notes |
|---|---|---|
@pytest.fixture | fixture inside class < Smartest::Fixture | Smartest fixtures are grouped in Ruby classes. |
| Test function parameter | Required keyword argument | `test("name") do |
| Fixture parameter | Fixture block keyword argument | `fixture :client do |
scope="session" | suite_fixture | Created lazily and reused for the suite. |
autouse=True setup with monkeypatch | Optional around_test or around_suite method stub | Intentionally broad setup applies without a stub-only test keyword. |
yield teardown or finalizer | on_teardown | Register teardown immediately after acquiring the resource. |
conftest.py discovery | require fixture files and use_fixture | Smartest 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.