diff --git a/src/_zkapauthorizer/lease_maintenance.py b/src/_zkapauthorizer/lease_maintenance.py
index ea4e9206dae09887468883f8550e86f3099e72e3..dfb251db184171c3dfb8d0785b061013e515d0ce 100644
--- a/src/_zkapauthorizer/lease_maintenance.py
+++ b/src/_zkapauthorizer/lease_maintenance.py
@@ -256,16 +256,14 @@ class _FuzzyTimerService(Service):
         Schedule the next run of the operation.
         """
         self._call = self.reactor.callLater(
-            self.sample_interval_distribution(),
+            self.sample_interval_distribution().total_seconds(),
             self._iterate,
         )
 
 
 def lease_maintenance_service(
+        maintain_leases,
         reactor,
-        root_node,
-        storage_broker,
-        secret_holder,
         last_run,
         random,
         interval_mean=None,
@@ -278,17 +276,6 @@ def lease_maintenance_service(
     :param IReactorClock reactor: A Twisted reactor for scheduling renewal
         activity.
 
-    :param IFilesystemNode root_node: A Tahoe-LAFS filesystem node to use as
-        the root of a node hierarchy to be maintained.
-
-    :param StorageFarmBroker storage_broker: The storage broker which can put
-        us in touch with storage servers where shares of the nodes to maintain
-        might be found.
-
-    :param SecretHolder secret_holder: The Tahoe-LAFS client node secret
-        holder which can give us the lease renewal secrets needed to renew
-        leases.
-
     :param datetime last_run: The time at which lease maintenance last ran to
         inform an adjustment to the first interval before running it again, or
         ``None`` not to make such an adjustment.
@@ -300,6 +287,9 @@ def lease_maintenance_service(
 
     :param timedelta interval_range: The range of the uniform distribution of
         lease renewal checks (centered on ``interval_mean``).
+
+    :param maintain_leases: A no-argument callable which performs a round of
+        lease-maintenance.  The resulting service calls this periodically.
     """
     if interval_mean is None:
         interval_mean = timedelta(days=26)
@@ -307,7 +297,6 @@ def lease_maintenance_service(
         interval_range = timedelta(days=4)
     halfrange = interval_range / 2
 
-    min_lease_remaining = timedelta(days=3)
     def sample_interval_distribution():
         return timedelta(
             seconds=random.uniform(
@@ -327,20 +316,41 @@ def lease_maintenance_service(
             initial_interval,
             timedelta(0),
         )
+
     return _FuzzyTimerService(
-        partial(
-            renew_leases,
-            partial(visit_filesystem_nodes, root_node),
-            storage_broker,
-            secret_holder,
-            min_lease_remaining,
-            lambda: datetime.utcfromtimestamp(reactor.seconds()),
-        ),
+        maintain_leases,
         initial_interval,
         sample_interval_distribution,
         reactor,
     )
 
+
+def maintain_leases_from_root(root_node, storage_broker, secret_holder, min_lease_remaining, get_now):
+    """
+    :param IFilesystemNode root_node: A Tahoe-LAFS filesystem node to use as
+        the root of a node hierarchy to be maintained.
+
+    :param StorageFarmBroker storage_broker: The storage broker which can put
+        us in touch with storage servers where shares of the nodes to maintain
+        might be found.
+
+    :param SecretHolder secret_holder: The Tahoe-LAFS client node secret
+        holder which can give us the lease renewal secrets needed to renew
+        leases.
+
+    :param get_now: A no-argument callable that returns the current time as a
+        ``datetime`` instance.
+    """
+    return partial(
+        renew_leases,
+        partial(visit_filesystem_nodes, root_node),
+        storage_broker,
+        secret_holder,
+        min_lease_remaining,
+        get_now,
+    )
+
+
 def calculate_initial_interval(sample_interval_distribution, last_run, now):
     """
     Determine how long to wait before performing an initial (for this process)
diff --git a/src/_zkapauthorizer/tests/test_lease_maintenance.py b/src/_zkapauthorizer/tests/test_lease_maintenance.py
index 27b5e7d833c57f5f62967b5b394ff7686b860a00..670fd47d17135071fcb1c184a3a91995417f9922 100644
--- a/src/_zkapauthorizer/tests/test_lease_maintenance.py
+++ b/src/_zkapauthorizer/tests/test_lease_maintenance.py
@@ -31,6 +31,9 @@ import attr
 from testtools import (
     TestCase,
 )
+from testtools.matchers import (
+    Equals,
+)
 from hypothesis import (
     given,
     note,
@@ -58,9 +61,9 @@ from twisted.application.service import (
 from allmydata.util.hashutil import (
     CRYPTO_VAL_SIZE,
 )
-from allmydata.client import (
-    SecretHolder,
-)
+# from allmydata.client import (
+#     SecretHolder,
+# )
 
 from ..foolscap import (
     ShareStat,
@@ -97,6 +100,10 @@ def interval_means():
     )
 
 
+def dummy_maintain_leases():
+    pass
+
+
 @attr.s
 class DummyStorageServer(object):
     """
@@ -171,14 +178,9 @@ class LeaseMaintenanceServiceTests(TestCase):
         The service provides ``IService``.
         """
         clock = Clock()
-        root_node = object()
-        lease_secret = b"\0" * CRYPTO_VAL_SIZE
-        convergence_secret = b"\1" * CRYPTO_VAL_SIZE
         service = lease_maintenance_service(
+            dummy_maintain_leases,
             clock,
-            root_node,
-            DummyStorageBroker(clock, []),
-            SecretHolder(lease_secret, convergence_secret),
             None,
             random,
         )
@@ -199,20 +201,14 @@ class LeaseMaintenanceServiceTests(TestCase):
         with a size of ``range``.
         """
         clock = Clock()
-        root_node = object()
-        lease_secret = b"\0" * CRYPTO_VAL_SIZE
-        convergence_secret = b"\1" * CRYPTO_VAL_SIZE
-
         # Construct a range that fits in with the mean
         range_ = timedelta(
             seconds=random.uniform(0, mean.total_seconds()),
         )
 
         service = lease_maintenance_service(
+            dummy_maintain_leases,
             clock,
-            root_node,
-            DummyStorageBroker(clock, []),
-            SecretHolder(lease_secret, convergence_secret),
             None,
             random,
             mean,
@@ -243,10 +239,6 @@ class LeaseMaintenanceServiceTests(TestCase):
         run.
         """
         datetime_now = datetime.utcfromtimestamp(clock.seconds())
-        root_node = object()
-        lease_secret = b"\0" * CRYPTO_VAL_SIZE
-        convergence_secret = b"\1" * CRYPTO_VAL_SIZE
-
         # Construct a range that fits in with the mean
         range_ = timedelta(
             seconds=random.uniform(0, mean.total_seconds()),
@@ -256,10 +248,8 @@ class LeaseMaintenanceServiceTests(TestCase):
         last_run = datetime_now - since_last_run
 
         service = lease_maintenance_service(
+            dummy_maintain_leases,
             clock,
-            root_node,
-            DummyStorageBroker(clock, []),
-            SecretHolder(lease_secret, convergence_secret),
             last_run,
             random,
             mean,
@@ -288,3 +278,30 @@ class LeaseMaintenanceServiceTests(TestCase):
             datetime.utcfromtimestamp(maintenance_call.getTime()),
             between(low, high),
         )
+
+    @given(
+        randoms(),
+        clocks(),
+    )
+    def test_nodes_visited(self, random, clock):
+        """
+        When the service runs, it calls the ``maintain_leases`` object.
+        """
+        leases_maintained_at = []
+        def maintain_leases():
+            leases_maintained_at.append(datetime.utcfromtimestamp(clock.seconds()))
+
+        service = lease_maintenance_service(
+            maintain_leases,
+            clock,
+            None,
+            random,
+        )
+        service.startService()
+        [maintenance_call] = clock.getDelayedCalls()
+        clock.advance(maintenance_call.getTime() - clock.seconds())
+
+        self.assertThat(
+            leases_maintained_at,
+            Equals([datetime.utcfromtimestamp(clock.seconds())]),
+        )