Sometimes you want to test methods of a class individually β but that gets tricky if those methods depend on the full class context (instantiation, parameters, etc.).
So how do you test a method before the class is ready β or before the method is even part of the class?
One way β But Itβs a Bit Clunky
from dataclasses import dataclass
from fastcore.basics import patch_to
from types import SimpleNamespace
from fastcore.test import test_eq
@dataclass
class Animal:
name: str
sound: str
# Define a raw FUNCTION
def speak_raw(self: Animal) -> str:
return f"{self.name} says {self.sound}"
# β
Test the raw FUNCTION with a dummy object
dummy = SimpleNamespace(name="Lion", sound="Roar")
test_eq(speak_raw(dummy), "Lion says Roar")
# π§ Patch it into the class
@patch_to(Animal)
def speak(self) -> str:
return speak_raw(self)
# β
Use it like a regular method
a = Animal(name="Dog", sound="Woof")
test_eq(a.speak(), "Dog says Woof")
print(a.speak()) # "Dog says Woof"
But thereβs one awkward bit: you need two different names (speak_raw
, speak
) because @patch_to
replaces the original name in the global namespace. Re-using the same name would lead to errors or infinite recursion (I hope I got the explanation right).
The Cleaner Way β Test First, Then Patch
Today I had an idea.
Hereβs the key insight: you can define your method as a regular function, test it, and only later attach it using patch_to
β all while keeping the same name.
0. Define Your Class
from dataclasses import dataclass
@dataclass
class Animal:
name: str
sound: str
1. Define the Method as a Function
def speak(self: Animal) -> str:
return f"{self.name} says {self.sound}"
2. Test the Function with a Dummy Object
from types import SimpleNamespace
from fastcore.test import test_eq
dummy = SimpleNamespace(name="Lion", sound="Roar")
test_eq(speak(dummy), "Lion says Roar")
3. Attach the Function as a Method Using patch_to
from fastcore.basics import patch_to
patch_to(Animal)(speak) # β This is the magic moment
4. Use It as a Method
a = Animal(name="Dog", sound="Woof")
test_eq(a.speak(), "Dog says Woof")
print(a.speak()) # "Dog says Woof"
Maybe you knew this all along. I havenβt seen it before and I wish I had.
Full Example (for easy copy-paste / testing)
#|export
from dataclasses import dataclass
from types import SimpleNamespace
from fastcore.test import test_eq
from fastcore.basics import patch_to
@dataclass
class Animal:
name: str
sound: str
# Define the method as a testable function
def speak(self: Animal) -> str:
return f"{self.name} says {self.sound}"
# β
Test the function
dummy = SimpleNamespace(name="Lion", sound="Roar")
test_eq(speak(dummy), "Lion says Roar")
# π§ Patch it into the class
patch_to(Animal)(speak)
# β
Now use it like a method
a = Animal(name="Dog", sound="Woof")
test_eq(a.speak(), "Dog says Woof")
print(a.speak())
Why This Matters
Enables test-first development, even with instance-bound logic.
Reduces coupling to class state until the function is stable.
Avoids namespace clobbering or recursion bugs with
@patch_to
.Perfectly suited for
nbdev
or interactive notebook workflows.