diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py
index 8691fface49804e256e2a003930cb9035c155da7..3cb8204f300cc894b3d40ff1d80aed4b0d0e2d0b 100644
--- a/src/_zkapauthorizer/model.py
+++ b/src/_zkapauthorizer/model.py
@@ -94,6 +94,13 @@ class StoreOpenError(Exception):
         self.reason = reason
 
 
+class NotEnoughTokens(Exception):
+    """
+    An attempt to extract tokens failed because the store does not contain as
+    many tokens as were requested.
+    """
+
+
 CONFIG_DB_NAME = u"privatestorageio-zkapauthz-v1.sqlite3"
 
 def open_and_initialize(path, connect=None):
@@ -396,6 +403,16 @@ class VoucherStore(object):
 
         :return list[UnblindedTokens]: The removed unblinded tokens.
         """
+        cursor.execute(
+            """
+            SELECT COUNT(token)
+            FROM [unblinded-tokens]
+            """,
+        )
+        [(existing_tokens,)] = cursor.fetchall()
+        if existing_tokens < count:
+            raise NotEnoughTokens()
+
         cursor.execute(
             """
             CREATE TEMPORARY TABLE [extracting]
diff --git a/src/_zkapauthorizer/tests/_exception.py b/src/_zkapauthorizer/tests/_exception.py
index 504e383cb5a664cee0a0c11449721072e3bb80c4..5e8bc8f5b21c29ebba4e93528a417761ff3fcf1d 100644
--- a/src/_zkapauthorizer/tests/_exception.py
+++ b/src/_zkapauthorizer/tests/_exception.py
@@ -60,7 +60,7 @@ class MatchesExceptionType(Matcher):
         etype, evalue, etb = other
         if not issubclass(etype, expected_class):
             return Mismatch(
-                "{} is an instance of {}, expected an instance of {}.".format(
+                "{!r} is an instance of {}, expected an instance of {}.".format(
                     evalue,
                     etype,
                     expected_class,
diff --git a/src/_zkapauthorizer/tests/test_model.py b/src/_zkapauthorizer/tests/test_model.py
index c4885ed96367e4227a46dc080e2f66df1895d780..8929957edf209af5949778fa12d58af6b3807922 100644
--- a/src/_zkapauthorizer/tests/test_model.py
+++ b/src/_zkapauthorizer/tests/test_model.py
@@ -45,7 +45,6 @@ from testtools.matchers import (
     Equals,
     Raises,
     IsInstance,
-    raises,
 )
 
 from fixtures import (
@@ -75,6 +74,7 @@ from ..storage_common import (
 
 from ..model import (
     StoreOpenError,
+    NotEnoughTokens,
     VoucherStore,
     Voucher,
     Pending,
@@ -96,6 +96,9 @@ from .strategies import (
 from .fixtures import (
     TemporaryVoucherStore,
 )
+from .matchers import (
+    raises,
+)
 
 
 class VoucherStoreTests(TestCase):
@@ -367,14 +370,14 @@ class UnblindedTokenStoreTests(TestCase):
         Unblinded tokens for a voucher which has not been added to the store cannot be inserted.
         """
         store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store
-        try:
-            result = store.insert_unblinded_tokens_for_voucher(voucher_value, public_key, unblinded_tokens)
-        except ValueError:
-            pass
-        except Exception as e:
-            self.fail("insert_unblinded_tokens_for_voucher raised the wrong exception: {}".format(e))
-        else:
-            self.fail("insert_unblinded_tokens_for_voucher didn't raise, returned: {}".format(result))
+        self.assertThat(
+            lambda: store.insert_unblinded_tokens_for_voucher(
+                voucher_value,
+                public_key,
+                unblinded_tokens,
+            ),
+            raises(ValueError),
+        )
 
     @given(
         tahoe_configs(),
@@ -395,8 +398,10 @@ class UnblindedTokenStoreTests(TestCase):
         self.expectThat(unblinded_tokens, AfterPreprocessing(sorted, Equals(retrieved_tokens)))
 
         # After extraction, the unblinded tokens are no longer available.
-        more_unblinded_tokens = store.extract_unblinded_tokens(1)
-        self.expectThat([], Equals(more_unblinded_tokens))
+        self.assertThat(
+            lambda: store.extract_unblinded_tokens(1),
+            raises(NotEnoughTokens),
+        )
 
     @given(
         tahoe_configs(),
@@ -498,14 +503,10 @@ class UnblindedTokenStoreTests(TestCase):
         store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store
         store.add(voucher_value, lambda: random)
         store.insert_unblinded_tokens_for_voucher(voucher_value, public_key, unblinded)
-        try:
-            result = store.mark_voucher_double_spent(voucher_value)
-        except ValueError:
-            pass
-        except Exception as e:
-            self.fail("mark_voucher_double_spent raised the wrong exception: {}".format(e))
-        else:
-            self.fail("mark_voucher_double_spent didn't raise, returned: {}".format(result))
+        self.assertThat(
+            lambda: store.mark_voucher_double_spent(voucher_value),
+            raises(ValueError),
+        )
 
     @given(
         tahoe_configs(),
@@ -517,14 +518,49 @@ class UnblindedTokenStoreTests(TestCase):
         A voucher which is not known cannot be marked as double-spent.
         """
         store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store
-        try:
-            result = store.mark_voucher_double_spent(voucher_value)
-        except ValueError:
-            pass
-        except Exception as e:
-            self.fail("mark_voucher_double_spent raised the wrong exception: {}".format(e))
-        else:
-            self.fail("mark_voucher_double_spent didn't raise, returned: {}".format(result))
+        self.assertThat(
+            lambda: store.mark_voucher_double_spent(voucher_value),
+            raises(ValueError),
+        )
+
+    @given(
+        tahoe_configs(),
+        datetimes(),
+        vouchers(),
+        dummy_ristretto_keys(),
+        integers(min_value=1, max_value=100),
+        integers(min_value=1),
+        data(),
+    )
+    def test_not_enough_unblinded_tokens(self, get_config, now, voucher_value, public_key, num_tokens, extra, data):
+        """
+        ``extract_unblinded_tokens`` raises ``NotEnoughTokens`` if ``count`` is
+        greater than the number of unblinded tokens in the store.
+        """
+        random = data.draw(
+            lists(
+                random_tokens(),
+                min_size=num_tokens,
+                max_size=num_tokens,
+                unique=True,
+            ),
+        )
+        unblinded = data.draw(
+            lists(
+                unblinded_tokens(),
+                min_size=num_tokens,
+                max_size=num_tokens,
+                unique=True,
+            ),
+        )
+        store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store
+        store.add(voucher_value, lambda: random)
+        store.insert_unblinded_tokens_for_voucher(voucher_value, public_key, unblinded)
+
+        self.assertThat(
+            lambda: store.extract_unblinded_tokens(num_tokens + extra),
+            raises(NotEnoughTokens),
+        )
 
 
     # TODO: Other error states and transient states
diff --git a/src/_zkapauthorizer/tests/test_plugin.py b/src/_zkapauthorizer/tests/test_plugin.py
index 6fb6a13963c7f4ca92ae36f4e0d91266da5f91ab..48f8cfcee9ceed36b72dc9df7dd42953c1eee11f 100644
--- a/src/_zkapauthorizer/tests/test_plugin.py
+++ b/src/_zkapauthorizer/tests/test_plugin.py
@@ -42,7 +42,6 @@ from testtools.matchers import (
     Always,
     Contains,
     AfterPreprocessing,
-    Equals,
 )
 from testtools.twistedsupport import (
     succeeded,
@@ -100,6 +99,7 @@ from ..foolscap import (
     RIPrivacyPassAuthorizedStorageServer,
 )
 from ..model import (
+    NotEnoughTokens,
     VoucherStore,
 )
 from ..controller import (
@@ -107,6 +107,10 @@ from ..controller import (
     PaymentController,
     DummyRedeemer,
 )
+from ..storage_common import (
+    BYTES_PER_PASS,
+    required_passes,
+)
 from .._storage_client import (
     IncorrectStorageServerReference,
 )
@@ -318,14 +322,14 @@ class ClientPluginTests(TestCase):
         node_config.config.write(config_text)
         self.addDetail(u"config", text_content(config_text.getvalue()))
         self.addDetail(u"announcement", text_content(unicode(announcement)))
-        try:
-            result = storage_server.get_storage_client(node_config, announcement, get_rref)
-        except IssuerConfigurationMismatch:
-            pass
-        except Exception as e:
-            self.fail("get_storage_client raised the wrong exception: {}".format(e))
-        else:
-            self.fail("get_storage_client didn't raise, returned: {}".format(result))
+        self.assertThat(
+            lambda: storage_server.get_storage_client(
+                node_config,
+                announcement,
+                get_rref,
+            ),
+            raises(IssuerConfigurationMismatch),
+        )
 
 
     @given(
@@ -418,7 +422,8 @@ class ClientPluginTests(TestCase):
         controller = PaymentController(
             store,
             DummyRedeemer(),
-            1,
+            # Give it enough for the allocate_buckets call below.
+            required_passes(BYTES_PER_PASS, [size] * len(sharenums)),
         )
         # Get a token inserted into the store.
         redeeming = controller.redeem(voucher)
@@ -445,10 +450,9 @@ class ClientPluginTests(TestCase):
         )
 
         # There should be no unblinded tokens left to extract.
-        remaining = store.extract_unblinded_tokens(1)
         self.assertThat(
-            remaining,
-            Equals([]),
+            lambda: store.extract_unblinded_tokens(1),
+            raises(NotEnoughTokens),
         )