Repository: wtforms/wtforms
Pull request #540 (2020-02-01 07:56:06)

Allow consecutive dashes in hostname
fixes: #529 
This allows consecutive dashed in hostname. Thus, http://foo--bar.dk/ is validated as valid URL.


Patch:
diff --git a/src/wtforms/fields/core.py b/src/wtforms/fields/core.py
index 71e59ab..c3018e9 100644
--- a/src/wtforms/fields/core.py
+++ b/src/wtforms/fields/core.py
@@ -545,6 +545,9 @@ class SelectField(SelectFieldBase):
                 raise ValueError(self.gettext("Invalid Choice: could not coerce"))
 
     def pre_validate(self, form):
+        if self.choices is None:
+            raise TypeError(self.gettext("Choices cannot be None"))
+
         if self.validate_choice:
             for v, _ in self.choices:
                 if self.data == v:
diff --git a/src/wtforms/validators.py b/src/wtforms/validators.py
index b77c14b..f274bd5 100644
--- a/src/wtforms/validators.py
+++ b/src/wtforms/validators.py
@@ -615,7 +615,7 @@ class HostnameValidation(object):
     This is not a validator in and of itself, and as such is not exported.
     """
 
-    hostname_part = re.compile(r"^(xn-|[a-z0-9_]+)(-[a-z0-9_]+)*$", re.IGNORECASE)
+    hostname_part = re.compile(r"^(xn-|[a-z0-9_]+)(-[a-z0-9_-]+)*$", re.IGNORECASE)
     tld_part = re.compile(r"^([a-z]{2,20}|xn--([a-z0-9]+-)*[a-z0-9]+)$", re.IGNORECASE)
 
     def __init__(self, require_tld=True, allow_ip=False):
diff --git a/tox.ini b/tox.ini
index 6b6b0c1..3e168c7 100644
--- a/tox.ini
+++ b/tox.ini
@@ -9,7 +9,7 @@ envlist =
 passenv = LANG
 deps =
     pytest
-    coverage < 5.0
+    coverage
     babel
     email_validator
     py27: ipaddress


Test patch:
diff --git a/tests/test_csrf.py b/tests/test_csrf.py
index 0062eed..f6f03c0 100644
--- a/tests/test_csrf.py
+++ b/tests/test_csrf.py
@@ -5,7 +5,7 @@ import datetime
 from functools import partial
 import hashlib
 import hmac
-from unittest import TestCase
+import pytest
 
 from tests.common import DummyPostData
 from wtforms.csrf.core import CSRF
@@ -44,7 +44,7 @@ class SimplePopulateObject(object):
     csrf_token = None
 
 
-class DummyCSRFTest(TestCase):
+class TestDummyCSRF:
     class F(Form):
         class Meta:
             csrf = True
@@ -53,13 +53,14 @@ class DummyCSRFTest(TestCase):
         a = StringField()
 
     def test_base_class(self):
-        self.assertRaises(NotImplementedError, self.F, meta={"csrf_class": CSRF})
+        with pytest.raises(NotImplementedError):
+            self.F(meta={"csrf_class": CSRF})
 
     def test_basic_impl(self):
         form = self.F()
         assert "csrf_token" in form
         assert not form.validate()
-        self.assertEqual(form.csrf_token._value(), "dummytoken")
+        assert form.csrf_token._value() == "dummytoken"
         form = self.F(DummyPostData(csrf_token="dummytoken"))
         assert form.validate()
 
@@ -77,10 +78,10 @@ class DummyCSRFTest(TestCase):
         form = self.F(a="test", csrf_token="dummytoken")
         form.populate_obj(obj)
         assert obj.csrf_token is None
-        self.assertEqual(obj.a, "test")
+        assert obj.a == "test"
 
 
-class SessionCSRFTest(TestCase):
+class TestSessionCSRF:
     class F(Form):
         class Meta:
             csrf = True
@@ -97,8 +98,10 @@ class SessionCSRFTest(TestCase):
             csrf_class = TimePin
 
     def test_various_failures(self):
-        self.assertRaises(TypeError, self.F)
-        self.assertRaises(Exception, self.F, meta={"csrf_secret": None})
+        with pytest.raises(TypeError):
+            self.F()
+        with pytest.raises(Exception):
+            self.F(meta={"csrf_secret": None})
 
     def test_no_time_limit(self):
         session = {}
@@ -106,7 +109,7 @@ class SessionCSRFTest(TestCase):
         expected_csrf = hmac.new(
             b"foobar", session["csrf"].encode("ascii"), digestmod=hashlib.sha1
         ).hexdigest()
-        self.assertEqual(form.csrf_token.current_token, "##" + expected_csrf)
+        assert form.csrf_token.current_token == "##" + expected_csrf
         self._test_phase2(self.NoTimeLimit, session, form.csrf_token.current_token)
 
     def test_with_time_limit(self):
@@ -123,24 +126,22 @@ class SessionCSRFTest(TestCase):
         with TimePin.pin_time(dt(8, 11, 12)):
             form = self._test_phase1(self.Pinned, session)
             token = form.csrf_token.current_token
-            self.assertEqual(
-                token, "20130115084112##53812764d65abb8fa88384551a751ca590dff5fb"
-            )
+            assert token == "20130115084112##53812764d65abb8fa88384551a751ca590dff5fb"
 
         # Make sure that CSRF validates in a normal case.
         with TimePin.pin_time(dt(8, 18)):
             form = self._test_phase2(self.Pinned, session, token)
             new_token = form.csrf_token.current_token
-            self.assertNotEqual(new_token, token)
-            self.assertEqual(
-                new_token, "20130115084800##e399e3a6a84860762723672b694134507ba21b58"
+            assert new_token != token
+            assert (
+                new_token == "20130115084800##e399e3a6a84860762723672b694134507ba21b58"
             )
 
         # Make sure that CSRF fails when we're past time
         with TimePin.pin_time(dt(8, 43)):
             form = self._test_phase2(self.Pinned, session, token, False)
             assert not form.validate()
-            self.assertEqual(form.csrf_token.errors, ["CSRF token expired"])
+            assert form.csrf_token.errors == ["CSRF token expired"]
 
             # We can succeed with a slightly newer token
             self._test_phase2(self.Pinned, session, new_token)
diff --git a/tests/test_fields.py b/tests/test_fields.py
index beaf415..27561fc 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -4,7 +4,6 @@ from collections import namedtuple
 from datetime import date, datetime
 from decimal import Decimal, ROUND_DOWN, ROUND_UP
 import sys
-from unittest import TestCase
 
 from markupsafe import Markup
 import pytest
@@ -59,7 +58,7 @@ def make_form(_name="F", **fields):
     return type(str(_name), (Form,), fields)
 
 
-class DefaultsTest(TestCase):
+class TestDefaults:
     def test(self):
         expected = 42
 
@@ -68,150 +67,149 @@ class DefaultsTest(TestCase):
 
         test_value = StringField(default=expected).bind(Form(), "a")
         test_value.process(None)
-        self.assertEqual(test_value.data, expected)
+        assert test_value.data == expected
 
         test_callable = StringField(default=default_callable).bind(Form(), "a")
         test_callable.process(None)
-        self.assertEqual(test_callable.data, expected)
+        assert test_callable.data == expected
 
 
-class LabelTest(TestCase):
+class TestLabel:
     def test(self):
         expected = """<label for="test">Caption</label>"""
         label = Label("test", "Caption")
-        self.assertEqual(label(), expected)
-        self.assertEqual(str(label), expected)
-        self.assertEqual(text_type(label), expected)
-        self.assertEqual(label.__html__(), expected)
-        self.assertEqual(label().__html__(), expected)
-        self.assertEqual(label("hello"), """<label for="test">hello</label>""")
-        self.assertEqual(StringField("hi").bind(Form(), "a").label.text, "hi")
+        assert label() == expected
+        assert str(label) == expected
+        assert text_type(label) == expected
+        assert label.__html__() == expected
+        assert label().__html__() == expected
+        assert label("hello") == """<label for="test">hello</label>"""
+        assert StringField("hi").bind(Form(), "a").label.text == "hi"
         if PYTHON_VERSION < (3,):
-            self.assertEqual(repr(label), "Label(u'test', u'Caption')")
+            assert repr(label) == "Label(u'test', u'Caption')"
         else:
-            self.assertEqual(repr(label), "Label('test', 'Caption')")
-            self.assertEqual(label.__unicode__(), expected)
+            assert repr(label) == "Label('test', 'Caption')"
+            assert label.__unicode__() == expected
 
     def test_auto_label(self):
         t1 = StringField().bind(Form(), "foo_bar")
-        self.assertEqual(t1.label.text, "Foo Bar")
+        assert t1.label.text == "Foo Bar"
 
         t2 = StringField("").bind(Form(), "foo_bar")
-        self.assertEqual(t2.label.text, "")
+        assert t2.label.text == ""
 
     def test_override_for(self):
         label = Label("test", "Caption")
-        self.assertEqual(label(for_="foo"), """<label for="foo">Caption</label>""")
-        self.assertEqual(
-            label(**{"for": "bar"}), """<label for="bar">Caption</label>"""
-        )
+        assert label(for_="foo") == """<label for="foo">Caption</label>"""
+        assert label(**{"for": "bar"}) == """<label for="bar">Caption</label>"""
 
     def test_escaped_label_text(self):
         label = Label("test", '<script>alert("test");</script>')
-        self.assertEqual(
-            label(for_="foo"),
+        assert label(for_="foo") == (
             '<label for="foo">&lt;script&gt;'
-            "alert(&#34;test&#34;);&lt;/script&gt;</label>",
+            "alert(&#34;test&#34;);&lt;/script&gt;</label>"
         )
-        self.assertEqual(
-            label(**{"for": "bar"}),
+
+        assert label(**{"for": "bar"}) == (
             '<label for="bar">&lt;script&gt;'
-            "alert(&#34;test&#34;);&lt;/script&gt;</label>",
+            "alert(&#34;test&#34;);&lt;/script&gt;</label>"
         )
 
 
-class FlagsTest(TestCase):
-    def setUp(self):
-        t = StringField(validators=[validators.DataRequired()]).bind(Form(), "a")
-        self.flags = t.flags
+@pytest.fixture()
+def flags():
+    return StringField(validators=[validators.DataRequired()]).bind(Form(), "a").flags
 
-    def test_existing_values(self):
-        self.assertEqual(self.flags.required, True)
-        self.assertTrue("required" in self.flags)
-        self.assertEqual(self.flags.optional, False)
-        self.assertTrue("optional" not in self.flags)
 
-    def test_assignment(self):
-        self.assertTrue("optional" not in self.flags)
-        self.flags.optional = True
-        self.assertEqual(self.flags.optional, True)
-        self.assertTrue("optional" in self.flags)
+class TestFlags:
+    def test_existing_values(self, flags):
+        assert flags.required is True
+        assert "required" in flags
+        assert flags.optional is False
+        assert "optional" not in flags
 
-    def test_unset(self):
-        self.flags.required = False
-        self.assertEqual(self.flags.required, False)
-        self.assertTrue("required" not in self.flags)
+    def test_assignment(self, flags):
+        assert "optional" not in flags
+        flags.optional = True
+        assert flags.optional is True
+        assert "optional" in flags
 
-    def test_repr(self):
-        self.assertEqual(repr(self.flags), "<wtforms.fields.Flags: {required}>")
+    def test_unset(self, flags):
+        flags.required = False
+        assert flags.required is False
+        assert "required" not in flags
 
-    def test_underscore_property(self):
-        self.assertRaises(AttributeError, getattr, self.flags, "_foo")
-        self.flags._foo = 42
-        self.assertEqual(self.flags._foo, 42)
+    def test_repr(self, flags):
+        assert repr(flags) == "<wtforms.fields.Flags: {required}>"
 
+    def test_underscore_property(self, flags):
+        with pytest.raises(AttributeError):
+            flags._foo
+        flags._foo = 42
+        assert flags._foo == 42
 
-class UnsetValueTest(TestCase):
+
+class TestUnsetValue:
     def test(self):
-        self.assertEqual(str(unset_value), "<unset value>")
-        self.assertEqual(repr(unset_value), "<unset value>")
-        self.assertEqual(bool(unset_value), False)
+        assert str(unset_value) == "<unset value>"
+        assert repr(unset_value) == "<unset value>"
+        assert bool(unset_value) is False
         assert not unset_value
-        self.assertEqual(unset_value.__nonzero__(), False)
-        self.assertEqual(unset_value.__bool__(), False)
+        assert unset_value.__nonzero__() is False
+        assert unset_value.__bool__() is False
 
 
-class FiltersTest(TestCase):
+class TestFilters:
     class F(Form):
         a = StringField(default=" hello", filters=[lambda x: x.strip()])
         b = StringField(default="42", filters=[int, lambda x: -x])
 
     def test_working(self):
         form = self.F()
-        self.assertEqual(form.a.data, "hello")
-        self.assertEqual(form.b.data, -42)
+        assert form.a.data == "hello"
+        assert form.b.data == -42
         assert form.validate()
 
     def test_failure(self):
         form = self.F(DummyPostData(a=["  foo bar  "], b=["hi"]))
-        self.assertEqual(form.a.data, "foo bar")
-        self.assertEqual(form.b.data, "hi")
-        self.assertEqual(len(form.b.process_errors), 1)
+        assert form.a.data == "foo bar"
+        assert form.b.data == "hi"
+        assert len(form.b.process_errors) == 1
         assert not form.validate()
 
 
-class FieldTest(TestCase):
+class TestField:
     class F(Form):
         a = StringField(default="hello", render_kw={"readonly": True, "foo": "bar"})
         b = StringField(validators=[validators.InputRequired()])
 
-    def setUp(self):
-        self.field = self.F().a
-
     def test_unbound_field(self):
         unbound = self.F.a
         assert unbound.creation_counter != 0
         assert unbound.field_class is StringField
-        self.assertEqual(unbound.args, ())
-        self.assertEqual(
-            unbound.kwargs,
-            {"default": "hello", "render_kw": {"readonly": True, "foo": "bar"}},
-        )
+        assert unbound.args == ()
+        assert unbound.kwargs == {
+            "default": "hello",
+            "render_kw": {"readonly": True, "foo": "bar"},
+        }
         assert repr(unbound).startswith("<UnboundField(StringField")
 
     def test_htmlstring(self):
-        self.assertTrue(isinstance(self.field.__html__(), Markup))
+        assert isinstance(self.F().a.__html__(), Markup)
 
     def test_str_coerce(self):
-        self.assertTrue(isinstance(str(self.field), str))
-        self.assertEqual(str(self.field), str(self.field()))
+        field = self.F().a
+        assert isinstance(str(field), str)
+        assert str(field) == str(field)
 
     def test_unicode_coerce(self):
-        self.assertEqual(text_type(self.field), self.field())
+        field = self.F().a
+        assert text_type(field) == field()
 
     def test_process_formdata(self):
-        Field.process_formdata(self.field, [42])
-        self.assertEqual(self.field.data, 42)
+        field = self.F().a
+        Field.process_formdata(field, [42])
+        assert field.data == 42
 
     def test_meta_attribute(self):
         # Can we pass in meta via _form?
@@ -224,21 +222,22 @@ class FieldTest(TestCase):
         assert field.meta is form_meta
 
         # Do we fail if both _meta and _form are None?
-        self.assertRaises(TypeError, StringField, _name="foo", _form=None)
+        with pytest.raises(TypeError):
+            StringField(_name="foo", _form=None)
 
     def test_render_kw(self):
         form = self.F()
-        self.assertEqual(
-            form.a(),
-            '<input foo="bar" id="a" name="a" readonly type="text" value="hello">',
+        assert (
+            form.a()
+            == '<input foo="bar" id="a" name="a" readonly type="text" value="hello">'
         )
-        self.assertEqual(
-            form.a(foo="baz"),
-            '<input foo="baz" id="a" name="a" readonly type="text" value="hello">',
+        assert (
+            form.a(foo="baz")
+            == '<input foo="baz" id="a" name="a" readonly type="text" value="hello">'
         )
-        self.assertEqual(
-            form.a(foo="baz", readonly=False, other="hello"),
-            '<input foo="baz" id="a" name="a" other="hello" type="text" value="hello">',
+        assert form.a(foo="baz", readonly=False, other="hello") == (
+            '<input foo="baz" id="a" name="a" other="hello" '
+            'type="text" value="hello">'
         )
 
     def test_select_field_copies_choices(self):
@@ -257,29 +256,28 @@ class FieldTest(TestCase):
         f1.add_choice("a")
         f2.add_choice("b")
 
-        self.assertEqual(f1.items.choices, [("a", "a")])
-        self.assertEqual(f2.items.choices, [("b", "b")])
-        self.assertTrue(f1.items.choices is not f2.items.choices)
+        assert f1.items.choices == [("a", "a")]
+        assert f2.items.choices == [("b", "b")]
+        assert f1.items.choices is not f2.items.choices
 
     def test_required_flag(self):
         form = self.F()
-        self.assertEqual(
-            form.b(), '<input id="b" name="b" required type="text" value="">'
-        )
+        assert form.b() == '<input id="b" name="b" required type="text" value="">'
 
     def test_check_validators(self):
         v1 = "Not callable"
         v2 = validators.DataRequired
 
-        with self.assertRaisesRegexp(
+        with pytest.raises(
             TypeError,
-            "{} is not a valid validator because " "it is not callable".format(v1),
+            match=r"{} is not a valid validator because "
+            "it is not callable".format(v1),
         ):
             Field(validators=[v1])
 
-        with self.assertRaisesRegexp(
+        with pytest.raises(
             TypeError,
-            "{} is not a valid validator because "
+            match=r"{} is not a valid validator because "
             "it is a class, it should be an "
             "instance".format(v2),
         ):
@@ -300,7 +298,7 @@ class PrePostTestField(StringField):
             raise ValueError("Post-stopped")
 
 
-class PrePostValidationTest(TestCase):
+class TestPrePostValidation:
     class F(Form):
         a = PrePostTestField(validators=[validators.Length(max=1, message="too long")])
 
@@ -311,22 +309,22 @@ class PrePostValidationTest(TestCase):
 
     def test_pre_stop(self):
         a = self._init_field("long")
-        self.assertEqual(a.errors, ["too long"])
+        assert a.errors == ["too long"]
 
         stoponly = self._init_field("stoponly")
-        self.assertEqual(stoponly.errors, [])
+        assert stoponly.errors == []
 
         stopmessage = self._init_field("stopmessage")
-        self.assertEqual(stopmessage.errors, ["stop with message"])
+        assert stopmessage.errors == ["stop with message"]
 
     def test_post(self):
         a = self._init_field("p")
-        self.assertEqual(a.errors, ["Post"])
+        assert a.errors == ["Post"]
         stopped = self._init_field("stop-post")
-        self.assertEqual(stopped.errors, ["stop with message", "Post-stopped"])
+        assert stopped.errors == ["stop with message", "Post-stopped"]
 
 
-class SelectFieldTest(TestCase):
+class TestSelectField:
     class F(Form):
         a = SelectField(choices=[("a", "hello"), ("btest", "bye")], default="a")
         b = SelectField(
@@ -337,80 +335,88 @@ class SelectFieldTest(TestCase):
 
     def test_defaults(self):
         form = self.F()
-        self.assertEqual(form.a.data, "a")
-        self.assertEqual(form.b.data, None)
-        self.assertEqual(form.validate(), False)
-        self.assertEqual(
-            form.a(),
+        assert form.a.data == "a"
+        assert form.b.data is None
+        assert form.validate() is False
+        assert form.a() == (
             '<select id="a" name="a"><option selected value="a">hello</option>'
-            '<option value="btest">bye</option></select>',
+            '<option value="btest">bye</option></select>'
         )
-        self.assertEqual(
-            form.b(),
+        assert form.b() == (
             '<select id="b" name="b"><option value="1">Item 1</option>'
-            '<option value="2">Item 2</option></select>',
+            '<option value="2">Item 2</option></select>'
         )
 
     def test_with_data(self):
         form = self.F(DummyPostData(a=["btest"]))
-        self.assertEqual(form.a.data, "btest")
-        self.assertEqual(
-            form.a(),
+        assert form.a.data == "btest"
+        assert form.a() == (
             '<select id="a" name="a"><option value="a">hello</option>'
-            '<option selected value="btest">bye</option></select>',
+            '<option selected value="btest">bye</option></select>'
         )
 
     def test_value_coercion(self):
         form = self.F(DummyPostData(b=["2"]))
-        self.assertEqual(form.b.data, 2)
-        self.assertTrue(form.b.validate(form))
+        assert form.b.data == 2
+        assert form.b.validate(form)
         form = self.F(DummyPostData(b=["b"]))
-        self.assertEqual(form.b.data, None)
-        self.assertFalse(form.b.validate(form))
+        assert form.b.data is None
+        assert not form.b.validate(form)
 
     def test_iterable_options(self):
         form = self.F()
         first_option = list(form.a)[0]
-        self.assertTrue(isinstance(first_option, form.a._Option))
-        self.assertEqual(
-            list(text_type(x) for x in form.a),
-            [
-                '<option selected value="a">hello</option>',
-                '<option value="btest">bye</option>',
-            ],
-        )
-        self.assertTrue(isinstance(first_option.widget, widgets.Option))
-        self.assertTrue(isinstance(list(form.b)[0].widget, widgets.TextInput))
-        self.assertEqual(
-            first_option(disabled=True),
-            '<option disabled selected value="a">hello</option>',
+        assert isinstance(first_option, form.a._Option)
+        assert list(text_type(x) for x in form.a) == [
+            '<option selected value="a">hello</option>',
+            '<option value="btest">bye</option>',
+        ]
+        assert isinstance(first_option.widget, widgets.Option)
+        assert isinstance(list(form.b)[0].widget, widgets.TextInput)
+        assert (
+            first_option(disabled=True)
+            == '<option disabled selected value="a">hello</option>'
         )
 
     def test_default_coerce(self):
         F = make_form(a=SelectField(choices=[("a", "Foo")]))
         form = F(DummyPostData(a=[]))
         assert not form.validate()
-        self.assertEqual(form.a.data, None)
-        self.assertEqual(len(form.a.errors), 1)
-        self.assertEqual(form.a.errors[0], "Not a valid choice")
+        assert form.a.data is None
+        assert len(form.a.errors) == 1
+        assert form.a.errors[0] == "Not a valid choice"
 
     def test_validate_choices(self):
         F = make_form(a=SelectField(choices=[("a", "Foo")]))
         form = F(DummyPostData(a=["b"]))
         assert not form.validate()
+        assert form.a.data == "b"
+        assert len(form.a.errors) == 1
+        assert form.a.errors[0] == "Not a valid choice"
+
+    def test_validate_choices_when_empty(self):
+        F = make_form(a=SelectField(choices=[]))
+        form = F(DummyPostData(a=["b"]))
+        assert not form.validate()
         self.assertEqual(form.a.data, "b")
         self.assertEqual(len(form.a.errors), 1)
         self.assertEqual(form.a.errors[0], "Not a valid choice")
 
+    def test_validate_choices_when_none(self):
+        F = make_form(a=SelectField())
+        form = F(DummyPostData(a="b"))
+        with pytest.raises(TypeError, match="Choices cannot be None"):
+            form.validate()
+
     def test_dont_validate_choices(self):
         F = make_form(a=SelectField(choices=[("a", "Foo")], validate_choice=False))
         form = F(DummyPostData(a=["b"]))
         assert form.validate()
-        self.assertEqual(form.a.data, "b")
-        self.assertEqual(len(form.a.errors), 0)
+        assert form.a.data == "b"
+        assert len(form.a.errors) == 0
 
 
-class SelectMultipleFieldTest(TestCase):
+class TestSelectMultipleField:
     class F(Form):
         a = SelectMultipleField(
             choices=[("a", "hello"), ("b", "bye"), ("c", "something")], default=("a",)
@@ -421,78 +427,68 @@ class SelectMultipleFieldTest(TestCase):
 
     def test_defaults(self):
         form = self.F()
-        self.assertEqual(form.a.data, ["a"])
-        self.assertEqual(form.b.data, [1, 3])
+        assert form.a.data == ["a"]
+        assert form.b.data == [1, 3]
         # Test for possible regression with null data
         form.a.data = None
-        self.assertTrue(form.validate())
-        self.assertEqual(
-            list(form.a.iter_choices()), [(v, l, False) for v, l in form.a.choices]
-        )
+        assert form.validate()
+        assert list(form.a.iter_choices()) == [(v, l, False) for v, l in form.a.choices]
 
     def test_with_data(self):
         form = self.F(DummyPostData(a=["a", "c"]))
-        self.assertEqual(form.a.data, ["a", "c"])
-        self.assertEqual(
-            list(form.a.iter_choices()),
-            [("a", "hello", True), ("b", "bye", False), ("c", "something", True)],
-        )
-        self.assertEqual(form.b.data, [])
+        assert form.a.data == ["a", "c"]
+        assert list(form.a.iter_choices()) == [
+            ("a", "hello", True),
+            ("b", "bye", False),
+            ("c", "something", True),
+        ]
+        assert form.b.data == []
         form = self.F(DummyPostData(b=["1", "2"]))
-        self.assertEqual(form.b.data, [1, 2])
-        self.assertTrue(form.validate())
+        assert form.b.data == [1, 2]
+        assert form.validate()
         form = self.F(DummyPostData(b=["1", "2", "4"]))
-        self.assertEqual(form.b.data, [1, 2, 4])
-        self.assertFalse(form.validate())
+        assert form.b.data == [1, 2, 4]
+        assert not form.validate()
 
     def test_coerce_fail(self):
         form = self.F(b=["a"])
         assert form.validate()
-        self.assertEqual(form.b.data, None)
+        assert form.b.data is None
         form = self.F(DummyPostData(b=["fake"]))
         assert not form.validate()
-        self.assertEqual(form.b.data, [1, 3])
+        assert form.b.data == [1, 3]
 
 
-class RadioFieldTest(TestCase):
+class TestRadioField:
     class F(Form):
         a = RadioField(choices=[("a", "hello"), ("b", "bye")], default="a")
         b = RadioField(choices=[(1, "Item 1"), (2, "Item 2")], coerce=int)
 
     def test(self):
         form = self.F()
-        self.assertEqual(form.a.data, "a")
-        self.assertEqual(form.b.data, None)
-        self.assertEqual(form.validate(), False)
-        self.assertEqual(
-            form.a(),
-            (
-                '<ul id="a">'
-                '<li><input checked id="a-0" name="a" type="radio" value="a"> '
-                '<label for="a-0">hello</label></li>'
-                '<li><input id="a-1" name="a" type="radio" value="b"> '
-                '<label for="a-1">bye</label></li>'
-                "</ul>"
-            ),
-        )
-        self.assertEqual(
-            form.b(),
-            (
-                '<ul id="b">'
-                '<li><input id="b-0" name="b" type="radio" value="1"> '
-                '<label for="b-0">Item 1</label></li>'
-                '<li><input id="b-1" name="b" type="radio" value="2"> '
-                '<label for="b-1">Item 2</label></li>'
-                "</ul>"
-            ),
+        assert form.a.data == "a"
+        assert form.b.data is None
+        assert form.validate() is False
+        assert form.a() == (
+            '<ul id="a">'
+            '<li><input checked id="a-0" name="a" type="radio" value="a"> '
+            '<label for="a-0">hello</label></li>'
+            '<li><input id="a-1" name="a" type="radio" value="b"> '
+            '<label for="a-1">bye</label></li>'
+            "</ul>"
         )
-        self.assertEqual(
-            [text_type(x) for x in form.a],
-            [
-                '<input checked id="a-0" name="a" type="radio" value="a">',
-                '<input id="a-1" name="a" type="radio" value="b">',
-            ],
+        assert form.b() == (
+            '<ul id="b">'
+            '<li><input id="b-0" name="b" type="radio" value="1"> '
+            '<label for="b-0">Item 1</label></li>'
+            '<li><input id="b-1" name="b" type="radio" value="2"> '
+            '<label for="b-1">Item 2</label></li>'
+            "</ul>"
         )
+        assert [text_type(x) for x in form.a] == [
+            '<input checked id="a-0" name="a" type="radio" value="a">',
+            '<input id="a-1" name="a" type="radio" value="b">',
+        ]
 
     def test_text_coercion(self):
         # Regression test for text coercion scenarios where the value is a boolean.
@@ -503,58 +499,53 @@ class RadioFieldTest(TestCase):
             )
         )
         form = F()
-        self.assertEqual(
-            form.a(),
+        assert form.a() == (
             '<ul id="a">'
             '<li><input id="a-0" name="a" type="radio" value="True"> '
             '<label for="a-0">yes</label></li>'
             '<li><input id="a-1" name="a" type="radio" value="False"> '
             '<label for="a-1">no</label></li>'
-            "</ul>",
+            "</ul>"
         )
 
 
-class StringFieldTest(TestCase):
+class TestStringField:
     class F(Form):
         a = StringField()
 
     def test(self):
         form = self.F()
-        self.assertEqual(form.a.data, None)
-        self.assertEqual(form.a(), """<input id="a" name="a" type="text" value="">""")
+        assert form.a.data is None
+        assert form.a() == """<input id="a" name="a" type="text" value="">"""
         form = self.F(DummyPostData(a=["hello"]))
-        self.assertEqual(form.a.data, "hello")
-        self.assertEqual(
-            form.a(), """<input id="a" name="a" type="text" value="hello">"""
-        )
+        assert form.a.data == "hello"
+        assert form.a() == """<input id="a" name="a" type="text" value="hello">"""
         form = self.F(DummyPostData(b=["hello"]))
-        self.assertEqual(form.a.data, None)
+        assert form.a.data is None
 
 
-class HiddenFieldTest(TestCase):
+class TestHiddenField:
     class F(Form):
         a = HiddenField(default="LE DEFAULT")
 
     def test(self):
         form = self.F()
-        self.assertEqual(
-            form.a(), """<input id="a" name="a" type="hidden" value="LE DEFAULT">"""
+        assert (
+            form.a() == """<input id="a" name="a" type="hidden" value="LE DEFAULT">"""
         )
-        self.assertTrue(form.a.flags.hidden)
+        assert form.a.flags.hidden
 
 
-class TextAreaFieldTest(TestCase):
+class TestTextAreaField:
     class F(Form):
         a = TextAreaField(default="LE DEFAULT")
 
     def test(self):
         form = self.F()
-        self.assertEqual(
-            form.a(), """<textarea id="a" name="a">\r\nLE DEFAULT</textarea>"""
-        )
+        assert form.a() == """<textarea id="a" name="a">\r\nLE DEFAULT</textarea>"""
 
 
-class PasswordFieldTest(TestCase):
+class TestPasswordField:
     class F(Form):
         a = PasswordField(
             widget=widgets.PasswordInput(hide_value=False), default="LE DEFAULT"
@@ -563,99 +554,93 @@ class PasswordFieldTest(TestCase):
 
     def test(self):
         form = self.F()
-        self.assertEqual(
-            form.a(), """<input id="a" name="a" type="password" value="LE DEFAULT">"""
-        )
-        self.assertEqual(
-            form.b(), """<input id="b" name="b" type="password" value="">"""
+        assert (
+            form.a() == """<input id="a" name="a" type="password" value="LE DEFAULT">"""
         )
+        assert form.b() == """<input id="b" name="b" type="password" value="">"""
 
 
-class FileFieldTest(TestCase):
+class TestFileField:
     def test_file_field(self):
         class F(Form):
             file = FileField()
 
-        self.assertEqual(F(DummyPostData(file=["test.txt"])).file.data, "test.txt")
-        self.assertEqual(F(DummyPostData()).file.data, None)
-        self.assertEqual(
-            F(DummyPostData(file=["test.txt", "multiple.txt"])).file.data, "test.txt"
+        assert F(DummyPostData(file=["test.txt"])).file.data == "test.txt"
+        assert F(DummyPostData()).file.data is None
+        assert (
+            F(DummyPostData(file=["test.txt", "multiple.txt"])).file.data == "test.txt"
         )
 
     def test_multiple_file_field(self):
         class F(Form):
             files = MultipleFileField()
 
-        self.assertEqual(F(DummyPostData(files=["test.txt"])).files.data, ["test.txt"])
-        self.assertEqual(F(DummyPostData()).files.data, [])
-        self.assertEqual(
-            F(DummyPostData(files=["test.txt", "multiple.txt"])).files.data,
-            ["test.txt", "multiple.txt"],
-        )
+        assert F(DummyPostData(files=["test.txt"])).files.data == ["test.txt"]
+        assert F(DummyPostData()).files.data == []
+        assert F(DummyPostData(files=["test.txt", "multiple.txt"])).files.data == [
+            "test.txt",
+            "multiple.txt",
+        ]
 
     def test_file_field_without_file_input(self):
         class F(Form):
             file = FileField(widget=TextInput())
 
         f = F(DummyPostData(file=["test.txt"]))
-        self.assertEqual(f.file.data, "test.txt")
-        self.assertEqual(f.file(), '<input id="file" name="file" type="text">')
+        assert f.file.data == "test.txt"
+        assert f.file() == '<input id="file" name="file" type="text">'
 
 
-class IntegerFieldTest(TestCase):
+class TestIntegerField:
     class F(Form):
         a = IntegerField()
         b = IntegerField(default=48)
 
     def test(self):
         form = self.F(DummyPostData(a=["v"], b=["-15"]))
-        self.assertEqual(form.a.data, None)
-        self.assertEqual(form.a.raw_data, ["v"])
-        self.assertEqual(
-            form.a(), """<input id="a" name="a" type="number" value="v">"""
-        )
-        self.assertEqual(form.b.data, -15)
-        self.assertEqual(
-            form.b(), """<input id="b" name="b" type="number" value="-15">"""
-        )
-        self.assertTrue(not form.a.validate(form))
-        self.assertTrue(form.b.validate(form))
+        assert form.a.data is None
+        assert form.a.raw_data == ["v"]
+        assert form.a() == """<input id="a" name="a" type="number" value="v">"""
+        assert form.b.data == -15
+        assert form.b() == """<input id="b" name="b" type="number" value="-15">"""
+        assert not form.a.validate(form)
+        assert form.b.validate(form)
         form = self.F(DummyPostData(a=[], b=[""]))
-        self.assertEqual(form.a.data, None)
-        self.assertEqual(form.a.raw_data, [])
-        self.assertEqual(form.b.data, None)
-        self.assertEqual(form.b.raw_data, [""])
-        self.assertTrue(not form.validate())
-        self.assertEqual(len(form.b.process_errors), 1)
-        self.assertEqual(len(form.b.errors), 1)
+        assert form.a.data is None
+        assert form.a.raw_data == []
+        assert form.b.data is None
+        assert form.b.raw_data == [""]
+        assert not form.validate()
+        assert len(form.b.process_errors) == 1
+        assert len(form.b.errors) == 1
         form = self.F(b=9)
-        self.assertEqual(form.b.data, 9)
-        self.assertEqual(form.a._value(), "")
-        self.assertEqual(form.b._value(), "9")
+        assert form.b.data == 9
+        assert form.a._value() == ""
+        assert form.b._value() == "9"
         form = self.F(DummyPostData(), data=dict(b="v"))
-        self.assertEqual(form.b.data, None)
-        self.assertEqual(form.a._value(), "")
-        self.assertEqual(form.b._value(), "")
-        self.assertTrue(not form.validate())
-        self.assertEqual(len(form.b.process_errors), 1)
-        self.assertEqual(len(form.b.errors), 1)
+        assert form.b.data is None
+        assert form.a._value() == ""
+        assert form.b._value() == ""
+        assert not form.validate()
+        assert len(form.b.process_errors) == 1
+        assert len(form.b.errors) == 1
 
 
-class DecimalFieldTest(TestCase):
+class TestDecimalField:
     def test(self):
         F = make_form(a=DecimalField())
         form = F(DummyPostData(a="2.1"))
-        self.assertEqual(form.a.data, Decimal("2.1"))
-        self.assertEqual(form.a._value(), "2.1")
+        assert form.a.data == Decimal("2.1")
+        assert form.a._value() == "2.1"
         form.a.raw_data = None
-        self.assertEqual(form.a._value(), "2.10")
-        self.assertTrue(form.validate())
+        assert form.a._value() == "2.10"
+        assert form.validate()
         form = F(DummyPostData(a="2,1"), a=Decimal(5))
-        self.assertEqual(form.a.data, None)
-        self.assertEqual(form.a.raw_data, ["2,1"])
-        self.assertFalse(form.validate())
+        assert form.a.data is None
+        assert form.a.raw_data == ["2,1"]
+        assert not form.validate()
         form = F(DummyPostData(a="asdf"), a=Decimal(".21"))
-        self.assertEqual(form.a._value(), "asdf")
+        assert form.a._value() == "asdf"
         assert not form.validate()
 
     def test_quantize(self):
@@ -663,46 +648,44 @@ class DecimalFieldTest(TestCase):
             a=DecimalField(places=3, rounding=ROUND_UP), b=DecimalField(places=None)
         )
         form = F(a=Decimal("3.1415926535"))
-        self.assertEqual(form.a._value(), "3.142")
+        assert form.a._value() == "3.142"
         form.a.rounding = ROUND_DOWN
-        self.assertEqual(form.a._value(), "3.141")
-        self.assertEqual(form.b._value(), "")
+        assert form.a._value() == "3.141"
+        assert form.b._value() == ""
         form = F(a=3.14159265, b=72)
-        self.assertEqual(form.a._value(), "3.142")
-        self.assertTrue(isinstance(form.a.data, float))
-        self.assertEqual(form.b._value(), "72")
+        assert form.a._value() == "3.142"
+        assert isinstance(form.a.data, float)
+        assert form.b._value() == "72"
 
 
-class FloatFieldTest(TestCase):
+class TestFloatField:
     class F(Form):
         a = FloatField()
         b = FloatField(default=48.0)
 
     def test(self):
         form = self.F(DummyPostData(a=["v"], b=["-15.0"]))
-        self.assertEqual(form.a.data, None)
-        self.assertEqual(form.a.raw_data, ["v"])
-        self.assertEqual(form.a(), """<input id="a" name="a" type="text" value="v">""")
-        self.assertEqual(form.b.data, -15.0)
-        self.assertEqual(
-            form.b(), """<input id="b" name="b" type="text" value="-15.0">"""
-        )
-        self.assertFalse(form.a.validate(form))
-        self.assertTrue(form.b.validate(form))
+        assert form.a.data is None
+        assert form.a.raw_data == ["v"]
+        assert form.a() == """<input id="a" name="a" type="text" value="v">"""
+        assert form.b.data == -15.0
+        assert form.b() == """<input id="b" name="b" type="text" value="-15.0">"""
+        assert not form.a.validate(form)
+        assert form.b.validate(form)
         form = self.F(DummyPostData(a=[], b=[""]))
-        self.assertEqual(form.a.data, None)
-        self.assertEqual(form.a._value(), "")
-        self.assertEqual(form.b.data, None)
-        self.assertEqual(form.b.raw_data, [""])
-        self.assertFalse(form.validate())
-        self.assertEqual(len(form.b.process_errors), 1)
-        self.assertEqual(len(form.b.errors), 1)
+        assert form.a.data is None
+        assert form.a._value() == ""
+        assert form.b.data is None
+        assert form.b.raw_data == [""]
+        assert not form.validate()
+        assert len(form.b.process_errors) == 1
+        assert len(form.b.errors) == 1
         form = self.F(b=9.0)
-        self.assertEqual(form.b.data, 9.0)
-        self.assertEqual(form.b._value(), "9.0")
+        assert form.b.data == 9.0
+        assert form.b._value() == "9.0"
 
 
-class BooleanFieldTest(TestCase):
+class TestBooleanField:
     class BoringForm(Form):
         bool1 = BooleanField()
         bool2 = BooleanField(default=True, false_values=())
@@ -712,42 +695,42 @@ class BooleanFieldTest(TestCase):
     def test_defaults(self):
         # Test with no post data to make sure defaults work
         form = self.BoringForm()
-        self.assertEqual(form.bool1.raw_data, None)
-        self.assertEqual(form.bool1.data, False)
-        self.assertEqual(form.bool2.data, True)
+        assert form.bool1.raw_data is None
+        assert form.bool1.data is False
+        assert form.bool2.data is True
 
     def test_rendering(self):
         form = self.BoringForm(DummyPostData(bool2="x"))
-        self.assertEqual(
-            form.bool1(), '<input id="bool1" name="bool1" type="checkbox" value="y">'
+        assert (
+            form.bool1() == '<input id="bool1" name="bool1" type="checkbox" value="y">'
         )
-        self.assertEqual(
-            form.bool2(),
-            '<input checked id="bool2" name="bool2" type="checkbox" value="x">',
+        assert (
+            form.bool2()
+            == '<input checked id="bool2" name="bool2" type="checkbox" value="x">'
         )
-        self.assertEqual(form.bool2.raw_data, ["x"])
+        assert form.bool2.raw_data == ["x"]
 
     def test_with_postdata(self):
         form = self.BoringForm(DummyPostData(bool1=["a"]))
-        self.assertEqual(form.bool1.raw_data, ["a"])
-        self.assertEqual(form.bool1.data, True)
+        assert form.bool1.raw_data == ["a"]
+        assert form.bool1.data is True
         form = self.BoringForm(DummyPostData(bool1=["false"], bool2=["false"]))
-        self.assertEqual(form.bool1.data, False)
-        self.assertEqual(form.bool2.data, True)
+        assert form.bool1.data is False
+        assert form.bool2.data is True
 
     def test_with_model_data(self):
         form = self.BoringForm(obj=self.obj)
-        self.assertEqual(form.bool1.data, False)
-        self.assertEqual(form.bool1.raw_data, None)
-        self.assertEqual(form.bool2.data, True)
+        assert form.bool1.data is False
+        assert form.bool1.raw_data is None
+        assert form.bool2.data is True
 
     def test_with_postdata_and_model(self):
         form = self.BoringForm(DummyPostData(bool1=["y"]), obj=self.obj)
-        self.assertEqual(form.bool1.data, True)
-        self.assertEqual(form.bool2.data, False)
+        assert form.bool1.data is True
+        assert form.bool2.data is False
 
 
-class DateFieldTest(TestCase):
+class TestDateField:
     class F(Form):
         a = DateField()
         b = DateField(format="%m/%d %Y")
@@ -755,21 +738,21 @@ class DateFieldTest(TestCase):
     def test_basic(self):
         d = date(2008, 5, 7)
         form = self.F(DummyPostData(a=["2008-05-07"], b=["05/07", "2008"]))
-        self.assertEqual(form.a.data, d)
-        self.assertEqual(form.a._value(), "2008-05-07")
-        self.assertEqual(form.b.data, d)
-        self.assertEqual(form.b._value(), "05/07 2008")
+        assert form.a.data == d
+        assert form.a._value() == "2008-05-07"
+        assert form.b.data == d
+        assert form.b._value() == "05/07 2008"
 
     def test_failure(self):
         form = self.F(DummyPostData(a=["2008-bb-cc"], b=["hi"]))
         assert not form.validate()
-        self.assertEqual(len(form.a.process_errors), 1)
-        self.assertEqual(len(form.a.errors), 1)
-        self.assertEqual(len(form.b.errors), 1)
-        self.assertEqual(form.a.process_errors[0], "Not a valid date value")
+        assert len(form.a.process_errors) == 1
+        assert len(form.a.errors) == 1
+        assert len(form.b.errors) == 1
+        assert form.a.process_errors[0] == "Not a valid date value"
 
 
-class TimeFieldTest(TestCase):
+class TestTimeField:
     class F(Form):
         a = TimeField()
         b = TimeField(format="%H:%M")
@@ -778,23 +761,19 @@ class TimeFieldTest(TestCase):
         d = datetime(2008, 5, 5, 4, 30, 0, 0).time()
         # Basic test with both inputs
         form = self.F(DummyPostData(a=["4:30"], b=["04:30"]))
-        self.assertEqual(form.a.data, d)
-        self.assertEqual(
-            form.a(), """<input id="a" name="a" type="time" value="4:30">"""
-        )
-        self.assertEqual(form.b.data, d)
-        self.assertEqual(
-            form.b(), """<input id="b" name="b" type="time" value="04:30">"""
-        )
-        self.assertTrue(form.validate())
+        assert form.a.data == d
+        assert form.a() == """<input id="a" name="a" type="time" value="4:30">"""
+        assert form.b.data == d
+        assert form.b() == """<input id="b" name="b" type="time" value="04:30">"""
+        assert form.validate()
 
         # Test with a missing input
         form = self.F(DummyPostData(a=["04"]))
-        self.assertFalse(form.validate())
-        self.assertEqual(form.a.errors[0], "Not a valid time value")
+        assert not form.validate()
+        assert form.a.errors[0] == "Not a valid time value"
 
 
-class DateTimeFieldTest(TestCase):
+class TestDateTimeField:
     class F(Form):
         a = DateTimeField()
         b = DateTimeField(format="%Y-%m-%d %H:%M")
@@ -805,149 +784,159 @@ class DateTimeFieldTest(TestCase):
         form = self.F(
             DummyPostData(a=["2008-05-05", "04:30:00"], b=["2008-05-05 04:30"])
         )
-        self.assertEqual(form.a.data, d)
-        self.assertEqual(
-            form.a(),
-            """<input id="a" name="a" type="datetime" value="2008-05-05 04:30:00">""",
+        assert form.a.data == d
+        assert (
+            form.a()
+            == """<input id="a" name="a" type="datetime" value="2008-05-05 04:30:00">"""
         )
-        self.assertEqual(form.b.data, d)
-        self.assertEqual(
-            form.b(),
-            """<input id="b" name="b" type="datetime" value="2008-05-05 04:30">""",
+        assert form.b.data == d
+        assert (
+            form.b()
+            == """<input id="b" name="b" type="datetime" value="2008-05-05 04:30">"""
         )
-        self.assertTrue(form.validate())
+        assert form.validate()
 
         # Test with a missing input
         form = self.F(DummyPostData(a=["2008-05-05"]))
-        self.assertFalse(form.validate())
-        self.assertEqual(form.a.errors[0], "Not a valid datetime value")
+        assert not form.validate()
+        assert form.a.errors[0] == "Not a valid datetime value"
 
         form = self.F(a=d, b=d)
-        self.assertTrue(form.validate())
-        self.assertEqual(form.a._value(), "2008-05-05 04:30:00")
+        assert form.validate()
+        assert form.a._value() == "2008-05-05 04:30:00"
 
     def test_microseconds(self):
         d = datetime(2011, 5, 7, 3, 23, 14, 424200)
         F = make_form(a=DateTimeField(format="%Y-%m-%d %H:%M:%S.%f"))
         form = F(DummyPostData(a=["2011-05-07 03:23:14.4242"]))
-        self.assertEqual(d, form.a.data)
+        assert d == form.a.data
 
 
-class SubmitFieldTest(TestCase):
+class TestSubmitField:
     class F(Form):
         a = SubmitField("Label")
 
     def test(self):
-        self.assertEqual(
-            self.F().a(), """<input id="a" name="a" type="submit" value="Label">"""
-        )
+        assert self.F().a() == """<input id="a" name="a" type="submit" value="Label">"""
 
 
-class FormFieldTest(TestCase):
-    def setUp(self):
-        F = make_form(
-            a=StringField(validators=[validators.DataRequired()]), b=StringField()
-        )
-        self.F1 = make_form("F1", a=FormField(F))
-        self.F2 = make_form("F2", a=FormField(F, separator="::"))
+@pytest.fixture
+def F1():
+    F = make_form(
+        a=StringField(validators=[validators.DataRequired()]), b=StringField()
+    )
+    return make_form("F1", a=FormField(F))
+
+
+@pytest.fixture
+def F2():
+    F = make_form(
+        a=StringField(validators=[validators.DataRequired()]), b=StringField()
+    )
+    return make_form("F2", a=FormField(F, separator="::"))
+
 
-    def test_formdata(self):
-        form = self.F1(DummyPostData({"a-a": ["moo"]}))
-        self.assertEqual(form.a.form.a.name, "a-a")
-        self.assertEqual(form.a["a"].data, "moo")
-        self.assertEqual(form.a["b"].data, None)
-        self.assertTrue(form.validate())
+class TestFormField:
+    def test_formdata(self, F1):
+        form = F1(DummyPostData({"a-a": ["moo"]}))
+        assert form.a.form.a.name == "a-a"
+        assert form.a["a"].data == "moo"
+        assert form.a["b"].data is None
+        assert form.validate()
 
-    def test_iteration(self):
-        self.assertEqual([x.name for x in self.F1().a], ["a-a", "a-b"])
+    def test_iteration(self, F1):
+        assert [x.name for x in F1().a] == ["a-a", "a-b"]
 
-    def test_with_obj(self):
+    def test_with_obj(self, F1):
         obj = AttrDict(a=AttrDict(a="mmm"))
-        form = self.F1(obj=obj)
-        self.assertEqual(form.a.form.a.data, "mmm")
-        self.assertEqual(form.a.form.b.data, None)
+        form = F1(obj=obj)
+        assert form.a.form.a.data == "mmm"
+        assert form.a.form.b.data is None
         obj_inner = AttrDict(a=None, b="rawr")
         obj2 = AttrDict(a=obj_inner)
         form.populate_obj(obj2)
-        self.assertTrue(obj2.a is obj_inner)
-        self.assertEqual(obj_inner.a, "mmm")
-        self.assertEqual(obj_inner.b, None)
+        assert obj2.a is obj_inner
+        assert obj_inner.a == "mmm"
+        assert obj_inner.b is None
 
-    def test_widget(self):
-        self.assertEqual(
-            self.F1().a(),
+    def test_widget(self, F1):
+        assert F1().a() == (
             '<table id="a">'
             '<tr><th><label for="a-a">A</label></th>'
             '<td><input id="a-a" name="a-a" required type="text" value=""></td></tr>'
             '<tr><th><label for="a-b">B</label></th>'
             '<td><input id="a-b" name="a-b" type="text" value=""></td></tr>'
-            "</table>",
+            "</table>"
         )
 
-    def test_separator(self):
-        form = self.F2(DummyPostData({"a-a": "fake", "a::a": "real"}))
-        self.assertEqual(form.a.a.name, "a::a")
-        self.assertEqual(form.a.a.data, "real")
-        self.assertTrue(form.validate())
+    def test_separator(self, F2):
+        form = F2(DummyPostData({"a-a": "fake", "a::a": "real"}))
+        assert form.a.a.name == "a::a"
+        assert form.a.a.data == "real"
+        assert form.validate()
 
-    def test_no_validators_or_filters(self):
+    def test_no_validators_or_filters(self, F1):
         class A(Form):
-            a = FormField(self.F1, validators=[validators.DataRequired()])
+            a = FormField(F1, validators=[validators.DataRequired()])
 
-        self.assertRaises(TypeError, A)
+        with pytest.raises(TypeError):
+            A()
 
         class B(Form):
-            a = FormField(self.F1, filters=[lambda x: x])
+            a = FormField(F1, filters=[lambda x: x])
 
-        self.assertRaises(TypeError, B)
+        with pytest.raises(TypeError):
+            B()
 
         class C(Form):
-            a = FormField(self.F1)
+            a = FormField(F1)
 
             def validate_a(self, field):
                 pass
 
         form = C()
-        self.assertRaises(TypeError, form.validate)
+        with pytest.raises(TypeError):
+            form.validate()
 
-    def test_populate_missing_obj(self):
+    def test_populate_missing_obj(self, F1):
         obj = AttrDict(a=None)
         obj2 = AttrDict(a=AttrDict(a="mmm"))
-        form = self.F1()
-        self.assertRaises(TypeError, form.populate_obj, obj)
+        form = F1()
+        with pytest.raises(TypeError):
+            form.populate_obj(obj)
         form.populate_obj(obj2)
 
 
-class FieldListTest(TestCase):
+class TestFieldList:
     t = StringField(validators=[validators.DataRequired()])
 
     def test_form(self):
         F = make_form(a=FieldList(self.t))
         data = ["foo", "hi", "rawr"]
         a = F(a=data).a
-        self.assertEqual(a.entries[1].data, "hi")
-        self.assertEqual(a.entries[1].name, "a-1")
-        self.assertEqual(a.data, data)
-        self.assertEqual(len(a.entries), 3)
+        assert a.entries[1].data == "hi"
+        assert a.entries[1].name == "a-1"
+        assert a.data == data
+        assert len(a.entries) == 3
 
         pdata = DummyPostData(
             {"a-0": ["bleh"], "a-3": ["yarg"], "a-4": [""], "a-7": ["mmm"]}
         )
         form = F(pdata)
-        self.assertEqual(len(form.a.entries), 4)
-        self.assertEqual(form.a.data, ["bleh", "yarg", "", "mmm"])
-        self.assertFalse(form.validate())
+        assert len(form.a.entries) == 4
+        assert form.a.data == ["bleh", "yarg", "", "mmm"]
+        assert not form.validate()
 
         form = F(pdata, a=data)
-        self.assertEqual(form.a.data, ["bleh", "yarg", "", "mmm"])
-        self.assertFalse(form.validate())
+        assert form.a.data == ["bleh", "yarg", "", "mmm"]
+        assert not form.validate()
 
         # Test for formdata precedence
         pdata = DummyPostData({"a-0": ["a"], "a-1": ["b"]})
         form = F(pdata, a=data)
-        self.assertEqual(len(form.a.entries), 2)
-        self.assertEqual(form.a.data, ["a", "b"])
-        self.assertEqual(list(iter(form.a)), list(form.a.entries))
+        assert len(form.a.entries) == 2
+        assert form.a.data == ["a", "b"]
+        assert list(iter(form.a)) == list(form.a.entries)
 
     def test_enclosed_subform(self):
         def make_inner():
@@ -958,53 +947,57 @@ class FieldListTest(TestCase):
         )
         data = [{"a": "hello"}]
         form = F(a=data)
-        self.assertEqual(form.a.data, data)
-        self.assertTrue(form.validate())
+        assert form.a.data == data
+        assert form.validate()
         form.a.append_entry()
-        self.assertEqual(form.a.data, data + [{"a": None}])
-        self.assertFalse(form.validate())
+        assert form.a.data == data + [{"a": None}]
+        assert not form.validate()
 
         pdata = DummyPostData({"a-0": ["fake"], "a-0-a": ["foo"], "a-1-a": ["bar"]})
         form = F(pdata, a=data)
-        self.assertEqual(form.a.data, [{"a": "foo"}, {"a": "bar"}])
+        assert form.a.data == [{"a": "foo"}, {"a": "bar"}]
 
         inner_obj = make_inner()
         inner_list = [inner_obj]
         obj = AttrDict(a=inner_list)
         form.populate_obj(obj)
-        self.assertTrue(obj.a is not inner_list)
-        self.assertEqual(len(obj.a), 2)
-        self.assertTrue(obj.a[0] is inner_obj)
-        self.assertEqual(obj.a[0].a, "foo")
-        self.assertEqual(obj.a[1].a, "bar")
+        assert obj.a is not inner_list
+        assert len(obj.a) == 2
+        assert obj.a[0] is inner_obj
+        assert obj.a[0].a == "foo"
+        assert obj.a[1].a == "bar"
 
         # Test failure on populate
         obj2 = AttrDict(a=42)
-        self.assertRaises(TypeError, form.populate_obj, obj2)
+        with pytest.raises(TypeError):
+            form.populate_obj(obj2)
 
     def test_entry_management(self):
         F = make_form(a=FieldList(self.t))
         a = F(a=["hello", "bye"]).a
-        self.assertEqual(a.pop_entry().name, "a-1")
-        self.assertEqual(a.data, ["hello"])
+        assert a.pop_entry().name == "a-1"
+        assert a.data == ["hello"]
         a.append_entry("orange")
-        self.assertEqual(a.data, ["hello", "orange"])
-        self.assertEqual(a[-1].name, "a-1")
-        self.assertEqual(a.pop_entry().data, "orange")
-        self.assertEqual(a.pop_entry().name, "a-0")
-        self.assertRaises(IndexError, a.pop_entry)
+        assert a.data == ["hello", "orange"]
+        assert a[-1].name == "a-1"
+        assert a.pop_entry().data == "orange"
+        assert a.pop_entry().name == "a-0"
+        with pytest.raises(IndexError):
+            a.pop_entry()
 
     def test_min_max_entries(self):
         F = make_form(a=FieldList(self.t, min_entries=1, max_entries=3))
         a = F().a
-        self.assertEqual(len(a), 1)
-        self.assertEqual(a[0].data, None)
+        assert len(a) == 1
+        assert a[0].data is None
         big_input = ["foo", "flaf", "bar", "baz"]
-        self.assertRaises(AssertionError, F, a=big_input)
+        with pytest.raises(AssertionError):
+            F(a=big_input)
         pdata = DummyPostData(("a-%d" % i, v) for i, v in enumerate(big_input))
         a = F(pdata).a
-        self.assertEqual(a.data, ["foo", "flaf", "bar"])
-        self.assertRaises(AssertionError, a.append_entry)
+        assert a.data == ["foo", "flaf", "bar"]
+        with pytest.raises(AssertionError):
+            a.append_entry()
 
     def test_validators(self):
         def validator(form, field):
@@ -1019,18 +1012,18 @@ class FieldListTest(TestCase):
         fdata = DummyPostData({"a-0": ["hello"], "a-1": ["bye"], "a-2": ["test3"]})
         form = F(fdata)
         assert not form.validate()
-        self.assertEqual(form.a.errors, ["too many"])
+        assert form.a.errors == ["too many"]
 
         # Case 2: checking a value within.
         fdata["a-0"] = ["fail"]
         form = F(fdata)
         assert not form.validate()
-        self.assertEqual(form.a.errors, ["fail"])
+        assert form.a.errors == ["fail"]
 
         # Case 3: normal field validator still works
         form = F(DummyPostData({"a-0": [""]}))
         assert not form.validate()
-        self.assertEqual(form.a.errors, [["This field is required."]])
+        assert form.a.errors == [["This field is required."]]
 
     def test_no_filters(self):
         with pytest.raises(TypeError):
@@ -1043,19 +1036,19 @@ class FieldListTest(TestCase):
         F = make_form(a=FieldList(self.t))
         # fill form
         form = F(obj=obj)
-        self.assertEqual(len(form.a.entries), 3)
+        assert len(form.a.entries) == 3
         # pretend to submit form unchanged
         pdata = DummyPostData({"a-0": ["foo"], "a-1": ["hi"], "a-2": ["rawr"]})
         form.process(formdata=pdata)
         # check if data still the same
-        self.assertEqual(len(form.a.entries), 3)
-        self.assertEqual(form.a.data, data)
+        assert len(form.a.entries) == 3
+        assert form.a.data == data
 
     def test_errors(self):
         F = make_form(a=FieldList(self.t))
         form = F(DummyPostData({"a-0": ["a"], "a-1": ""}))
         assert not form.validate()
-        self.assertEqual(form.a.errors, [[], ["This field is required."]])
+        assert form.a.errors == [[], ["This field is required."]]
 
 
 class MyCustomField(StringField):
@@ -1066,7 +1059,7 @@ class MyCustomField(StringField):
         return super(MyCustomField, self).process_data(data)
 
 
-class CustomFieldQuirksTest(TestCase):
+class TestCustomFieldQuirks:
     class F(Form):
         a = MyCustomField()
         b = SelectFieldBase()
@@ -1079,10 +1072,11 @@ class CustomFieldQuirksTest(TestCase):
 
     def test_default_impls(self):
         f = self.F()
-        self.assertRaises(NotImplementedError, f.b.iter_choices)
+        with pytest.raises(NotImplementedError):
+            f.b.iter_choices()
 
 
-class HTML5FieldsTest(TestCase):
+class TestHTML5Fields:
     class F(Form):
         search = SearchField()
         telephone = TelField()
diff --git a/tests/test_form.py b/tests/test_form.py
index a88acea..ce17bdd 100644
--- a/tests/test_form.py
+++ b/tests/test_form.py
@@ -1,6 +1,6 @@
 from __future__ import unicode_literals
 
-from unittest import TestCase
+import pytest
 
 from wtforms.form import BaseForm, Form
 from wtforms.meta import DefaultMeta
@@ -9,7 +9,7 @@ from wtforms.validators import ValidationError
 from tests.common import DummyPostData
 
 
-class BaseFormTest(TestCase):
+class TestBaseForm:
     def get_form(self, **kwargs):
         def validate_test(form, field):
             if field.data != "foobar":
@@ -20,86 +20,90 @@ class BaseFormTest(TestCase):
     def test_data_proxy(self):
         form = self.get_form()
         form.process(test="foo")
-        self.assertEqual(form.data, {"test": "foo"})
+        assert form.data == {"test": "foo"}
 
     def test_errors_proxy(self):
         form = self.get_form()
         form.process(test="foobar")
         form.validate()
-        self.assertEqual(form.errors, {})
+        assert form.errors == {}
 
         form = self.get_form()
         form.process()
         form.validate()
-        self.assertEqual(form.errors, {"test": ["error"]})
+        assert form.errors == {"test": ["error"]}
 
     def test_contains(self):
         form = self.get_form()
-        self.assertTrue("test" in form)
-        self.assertTrue("abcd" not in form)
+        assert "test" in form
+        assert "abcd" not in form
 
     def test_field_removal(self):
         form = self.get_form()
         del form["test"]
-        self.assertRaises(AttributeError, getattr, form, "test")
-        self.assertTrue("test" not in form)
+        with pytest.raises(AttributeError):
+            form.test
+        assert "test" not in form
 
     def test_field_adding(self):
         form = self.get_form()
-        self.assertEqual(len(list(form)), 1)
+        assert len(list(form)) == 1
         form["foo"] = StringField()
-        self.assertEqual(len(list(form)), 2)
+        assert len(list(form)) == 2
         form.process(DummyPostData(foo=["hello"]))
-        self.assertEqual(form["foo"].data, "hello")
+        assert form["foo"].data == "hello"
         form["test"] = IntegerField()
-        self.assertTrue(isinstance(form["test"], IntegerField))
-        self.assertEqual(len(list(form)), 2)
-        self.assertRaises(AttributeError, getattr, form["test"], "data")
+        assert isinstance(form["test"], IntegerField)
+        assert len(list(form)) == 2
+        with pytest.raises(AttributeError):
+            form["test"].data
         form.process(DummyPostData(test=["1"]))
-        self.assertEqual(form["test"].data, 1)
-        self.assertEqual(form["foo"].data, None)
+        assert form["test"].data == 1
+        assert form["foo"].data is None
 
     def test_populate_obj(self):
         m = type(str("Model"), (object,), {})
         form = self.get_form()
         form.process(test="foobar")
         form.populate_obj(m)
-        self.assertEqual(m.test, "foobar")
-        self.assertEqual([k for k in dir(m) if not k.startswith("_")], ["test"])
+        assert m.test == "foobar"
+        assert [k for k in dir(m) if not k.startswith("_")] == ["test"]
 
     def test_prefixes(self):
         form = self.get_form(prefix="foo")
-        self.assertEqual(form["test"].name, "foo-test")
-        self.assertEqual(form["test"].short_name, "test")
-        self.assertEqual(form["test"].id, "foo-test")
+        assert form["test"].name == "foo-test"
+        assert form["test"].short_name == "test"
+        assert form["test"].id == "foo-test"
         form = self.get_form(prefix="foo.")
         form.process(DummyPostData({"foo.test": ["hello"], "test": ["bye"]}))
-        self.assertEqual(form["test"].data, "hello")
-        self.assertEqual(self.get_form(prefix="foo[")["test"].name, "foo[-test")
+        assert form["test"].data == "hello"
+        assert self.get_form(prefix="foo[")["test"].name == "foo[-test"
 
     def test_formdata_wrapper_error(self):
         form = self.get_form()
-        self.assertRaises(TypeError, form.process, [])
+        with pytest.raises(TypeError):
+            form.process([])
 
 
-class FormMetaTest(TestCase):
+class TestFormMeta:
     def test_monkeypatch(self):
         class F(Form):
             a = StringField()
 
-        self.assertEqual(F._unbound_fields, None)
+        assert F._unbound_fields is None
         F()
-        self.assertEqual(F._unbound_fields, [("a", F.a)])
+        assert F._unbound_fields == [("a", F.a)]
         F.b = StringField()
-        self.assertEqual(F._unbound_fields, None)
+        assert F._unbound_fields is None
         F()
-        self.assertEqual(F._unbound_fields, [("a", F.a), ("b", F.b)])
+        assert F._unbound_fields == [("a", F.a), ("b", F.b)]
         del F.a
-        self.assertRaises(AttributeError, lambda: F.a)
+        with pytest.raises(AttributeError):
+            F.a
         F()
-        self.assertEqual(F._unbound_fields, [("b", F.b)])
+        assert F._unbound_fields == [("b", F.b)]
         F._m = StringField()
-        self.assertEqual(F._unbound_fields, [("b", F.b)])
+        assert F._unbound_fields == [("b", F.b)]
 
     def test_subclassing(self):
         class A(Form):
@@ -113,10 +117,10 @@ class FormMetaTest(TestCase):
         A()
         B()
 
-        self.assertTrue(A.a is B.a)
-        self.assertTrue(A.c is not B.c)
-        self.assertEqual(A._unbound_fields, [("a", A.a), ("c", A.c)])
-        self.assertEqual(B._unbound_fields, [("a", B.a), ("b", B.b), ("c", B.c)])
+        assert A.a is B.a
+        assert A.c is not B.c
+        assert A._unbound_fields == [("a", A.a), ("c", A.c)]
+        assert B._unbound_fields == [("a", B.a), ("b", B.b), ("c", B.c)]
 
     def test_class_meta_reassign(self):
         class MetaA:
@@ -128,16 +132,16 @@ class FormMetaTest(TestCase):
         class F(Form):
             Meta = MetaA
 
-        self.assertEqual(F._wtforms_meta, None)
+        assert F._wtforms_meta is None
         assert isinstance(F().meta, MetaA)
         assert issubclass(F._wtforms_meta, MetaA)
         F.Meta = MetaB
-        self.assertEqual(F._wtforms_meta, None)
+        assert F._wtforms_meta is None
         assert isinstance(F().meta, MetaB)
         assert issubclass(F._wtforms_meta, MetaB)
 
 
-class FormTest(TestCase):
+class TestForm:
     class F(Form):
         test = StringField()
 
@@ -147,37 +151,40 @@ class FormTest(TestCase):
 
     def test_validate(self):
         form = self.F(test="foobar")
-        self.assertEqual(form.validate(), True)
+        assert form.validate() is True
 
         form = self.F()
-        self.assertEqual(form.validate(), False)
+        assert form.validate() is False
 
     def test_field_adding_disabled(self):
         form = self.F()
-        self.assertRaises(TypeError, form.__setitem__, "foo", StringField())
+        with pytest.raises(TypeError):
+            form.__setitem__("foo", StringField())
 
     def test_field_removal(self):
         form = self.F()
         del form.test
-        self.assertTrue("test" not in form)
-        self.assertEqual(form.test, None)
-        self.assertEqual(len(list(form)), 0)
+        assert "test" not in form
+        assert form.test is None
+        assert len(list(form)) == 0
         # Try deleting a nonexistent field
-        self.assertRaises(AttributeError, form.__delattr__, "fake")
+        with pytest.raises(AttributeError):
+            form.__delattr__("fake")
 
     def test_delattr_idempotency(self):
         form = self.F()
         del form.test
-        self.assertEqual(form.test, None)
+        assert form.test is None
 
         # Make sure deleting a normal attribute works
         form.foo = 9
         del form.foo
-        self.assertRaises(AttributeError, form.__delattr__, "foo")
+        with pytest.raises(AttributeError):
+            form.__delattr__("foo")
 
         # Check idempotency
         del form.test
-        self.assertEqual(form.test, None)
+        assert form.test is None
 
     def test_ordered_fields(self):
         class MyForm(Form):
@@ -185,28 +192,24 @@ class FormTest(TestCase):
             banana = StringField()
             kiwi = StringField()
 
-        self.assertEqual([x.name for x in MyForm()], ["strawberry", "banana", "kiwi"])
+        assert [x.name for x in MyForm()] == ["strawberry", "banana", "kiwi"]
         MyForm.apple = StringField()
-        self.assertEqual(
-            [x.name for x in MyForm()], ["strawberry", "banana", "kiwi", "apple"]
-        )
+        assert [x.name for x in MyForm()], ["strawberry", "banana", "kiwi", "apple"]
         del MyForm.banana
-        self.assertEqual([x.name for x in MyForm()], ["strawberry", "kiwi", "apple"])
+        assert [x.name for x in MyForm()] == ["strawberry", "kiwi", "apple"]
         MyForm.strawberry = StringField()
-        self.assertEqual([x.name for x in MyForm()], ["kiwi", "apple", "strawberry"])
+        assert [x.name for x in MyForm()] == ["kiwi", "apple", "strawberry"]
         # Ensure sort is stable: two fields with the same creation counter
         # should be subsequently sorted by name.
         MyForm.cherry = MyForm.kiwi
-        self.assertEqual(
-            [x.name for x in MyForm()], ["cherry", "kiwi", "apple", "strawberry"]
-        )
+        assert [x.name for x in MyForm()] == ["cherry", "kiwi", "apple", "strawberry"]
 
     def test_data_arg(self):
         data = {"test": "foo"}
         form = self.F(data=data)
-        self.assertEqual(form.test.data, "foo")
+        assert form.test.data == "foo"
         form = self.F(data=data, test="bar")
-        self.assertEqual(form.test.data, "bar")
+        assert form.test.data == "bar"
 
     def test_empty_formdata(self):
         """"If formdata is empty, field.process_formdata should still
@@ -220,17 +223,15 @@ class FormTest(TestCase):
         class F(Form):
             test = EmptyStringField()
 
-        self.assertEqual(F().test.data, None)
-        self.assertEqual(F(test="test").test.data, "test")
-        self.assertEqual(F(DummyPostData({"other": "other"})).test.data, "processed")
-        self.assertEqual(F(DummyPostData()).test.data, "processed")
-        self.assertEqual(F(DummyPostData(), test="test").test.data, "processed")
-        self.assertEqual(
-            F(DummyPostData({"test": "foo"}), test="test").test.data, "foo"
-        )
+        assert F().test.data is None
+        assert F(test="test").test.data == "test"
+        assert F(DummyPostData({"other": "other"})).test.data == "processed"
+        assert F(DummyPostData()).test.data == "processed"
+        assert F(DummyPostData(), test="test").test.data == "processed"
+        assert F(DummyPostData({"test": "foo"}), test="test").test.data == "foo"
 
 
-class MetaTest(TestCase):
+class TestMeta:
     class F(Form):
         class Meta:
             foo = 9
@@ -252,16 +253,18 @@ class MetaTest(TestCase):
     def test_basic(self):
         form = self.Basic()
         meta = form.meta
-        self.assertEqual(meta.foo, 9)
-        self.assertEqual(meta.bar, 8)
-        self.assertEqual(meta.csrf, False)
+        assert meta.foo == 9
+        assert meta.bar == 8
+        assert meta.csrf is False
         assert isinstance(meta, self.F.Meta)
         assert isinstance(meta, self.G.Meta)
-        self.assertEqual(
-            type(meta).__bases__,
-            (self.Basic.Meta, self.F.Meta, self.G.Meta, DefaultMeta),
+        assert type(meta).__bases__ == (
+            self.Basic.Meta,
+            self.F.Meta,
+            self.G.Meta,
+            DefaultMeta,
         )
 
     def test_missing_diamond(self):
         meta = self.MissingDiamond().meta
-        self.assertEqual(type(meta).__bases__, (self.F.Meta, self.G.Meta, DefaultMeta))
+        assert type(meta).__bases__ == (self.F.Meta, self.G.Meta, DefaultMeta)
diff --git a/tests/test_i18n.py b/tests/test_i18n.py
index 5324a1d..83edc15 100644
--- a/tests/test_i18n.py
+++ b/tests/test_i18n.py
@@ -1,6 +1,6 @@
 from __future__ import unicode_literals
 
-from unittest import TestCase
+import pytest
 from wtforms import form, StringField, validators
 from wtforms.i18n import get_translations
 
@@ -30,21 +30,20 @@ class Python2_Translator(object):
     ungettext = ngettext_lower
 
 
-class I18NTest(TestCase):
+class TestI18N:
     def test_failure(self):
-        self.assertRaises(IOError, get_translations, [])
+        with pytest.raises(IOError):
+            get_translations([])
 
     def test_us_translation(self):
         translations = get_translations(["en_US"])
-        self.assertEqual(
-            translations.gettext("Invalid Mac address."), "Invalid MAC address."
-        )
+        assert translations.gettext("Invalid Mac address.") == "Invalid MAC address."
 
     def _test_converter(self, translator):
         translations = get_translations([], getter=lambda x: translator)
-        self.assertEqual(translations.gettext("Foo"), "foo")
-        self.assertEqual(translations.ngettext("Foo", "Foos", 1), "foo")
-        self.assertEqual(translations.ngettext("Foo", "Foos", 2), "foos")
+        assert translations.gettext("Foo") == "foo"
+        assert translations.ngettext("Foo", "Foos", 1) == "foo"
+        assert translations.ngettext("Foo", "Foos", 2) == "foos"
         return translations
 
     def test_python2_wrap(self):
@@ -58,7 +57,7 @@ class I18NTest(TestCase):
         assert translations is translator
 
 
-class CoreFormTest(TestCase):
+class TestCoreForm:
     class F(form.Form):
         class Meta:
             locales = ["en_US", "en"]
@@ -78,25 +77,25 @@ class CoreFormTest(TestCase):
             form_class = self.F
         form = form_class(**form_kwargs)
         assert not form.validate()
-        self.assertEqual(form.a.errors[0], expected_error)
+        assert form.a.errors[0] == expected_error
         return form
 
     def test_defaults(self):
         # Test with the default language
         form = self._common_test("This field is required.", {})
         # Make sure we have a gettext translations context
-        self.assertNotEqual(form.a.gettext(""), "")
+        assert form.a.gettext("") != ""
 
         form = self._common_test("This field is required.", {}, self.F2)
         assert form.meta.get_translations(form) is None
         assert form.meta.locales is False
-        self.assertEqual(form.a.gettext(""), "")
+        assert form.a.gettext("") == ""
 
     def test_fallback(self):
         form = self._common_test(
             "This field is required.", dict(meta=dict(locales=False))
         )
-        self.assertEqual(form.a.gettext(""), "")
+        assert form.a.gettext("") == ""
 
     def test_override_languages(self):
         self._common_test(
@@ -139,7 +138,7 @@ class CoreFormTest(TestCase):
         )
 
 
-class TranslationsTest(TestCase):
+class TestTranslations:
     class F(form.Form):
         a = StringField(validators=[validators.Length(max=5)])
 
@@ -150,25 +149,22 @@ class TranslationsTest(TestCase):
 
         a = StringField("", [validators.Length(max=5)])
 
-    def setUp(self):
-        self.a = self.F().a
-
     def test_gettext(self):
         x = "foo"
-        self.assertTrue(self.a.gettext(x) is x)
+        assert self.F().a.gettext(x) is x
 
     def test_ngettext(self):
         def getit(n):
-            return self.a.ngettext("antelope", "antelopes", n)
+            return self.F().a.ngettext("antelope", "antelopes", n)
 
-        self.assertEqual(getit(0), "antelopes")
-        self.assertEqual(getit(1), "antelope")
-        self.assertEqual(getit(2), "antelopes")
+        assert getit(0) == "antelopes"
+        assert getit(1) == "antelope"
+        assert getit(2) == "antelopes"
 
     def test_validator_translation(self):
         form = self.F2(a="hellobye")
-        self.assertFalse(form.validate())
-        self.assertEqual(form.a.errors[0], "field cannot be longer than 5 characters.")
+        assert not form.validate()
+        assert form.a.errors[0] == "field cannot be longer than 5 characters."
         form = self.F(a="hellobye")
-        self.assertFalse(form.validate())
-        self.assertEqual(form.a.errors[0], "Field cannot be longer than 5 characters.")
+        assert not form.validate()
+        assert form.a.errors[0] == "Field cannot be longer than 5 characters."
diff --git a/tests/test_locale_babel.py b/tests/test_locale_babel.py
index 15db54b..f90c588 100644
--- a/tests/test_locale_babel.py
+++ b/tests/test_locale_babel.py
@@ -1,8 +1,8 @@
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 
+import pytest
 from decimal import Decimal, ROUND_UP
-from unittest import TestCase
 
 from tests.common import DummyPostData
 from wtforms import Form
@@ -10,7 +10,7 @@ from wtforms.fields import DecimalField
 from wtforms.utils import unset_value
 
 
-class TestLocaleDecimal(TestCase):
+class TestLocaleDecimal:
     class F(Form):
         class Meta:
             locales = ["hi_IN", "en_US"]
@@ -22,7 +22,7 @@ class TestLocaleDecimal(TestCase):
         if locales is not unset_value:
             meta = {"locales": locales}
         form = self.F(meta=meta, a=Decimal(val))
-        self.assertEqual(form.a._value(), expected)
+        assert form.a._value() == expected
 
     def test_typeerror(self):
         def build(**kw):
@@ -35,8 +35,11 @@ class TestLocaleDecimal(TestCase):
                 **kw
             )
 
-        self.assertRaises(TypeError, build, places=2)
-        self.assertRaises(TypeError, build, rounding=ROUND_UP)
+        with pytest.raises(TypeError):
+            build(places=2)
+
+        with pytest.raises(TypeError):
+            build(rounding=ROUND_UP)
 
     def test_formatting(self):
         val = Decimal("123456.789")
@@ -60,7 +63,7 @@ class TestLocaleDecimal(TestCase):
                 "Expected value %r to parse as a decimal, instead got %r"
                 % (raw_val, form.a.errors)
             )
-        self.assertEqual(form.a.data, expected)
+        assert form.a.data == expected
 
     def _fail_parse(self, raw_val, expected_error, locales=unset_value):
         meta = None
@@ -68,7 +71,7 @@ class TestLocaleDecimal(TestCase):
             meta = {"locales": locales}
         form = self.F(DummyPostData(a=raw_val), meta=meta)
         assert not form.validate()
-        self.assertEqual(form.a.errors[0], expected_error)
+        assert form.a.errors[0] == expected_error
 
     def test_parsing(self):
         expected = Decimal("123456.789")
diff --git a/tests/test_validators.py b/tests/test_validators.py
index bf5e5ba..e223c8d 100644
--- a/tests/test_validators.py
+++ b/tests/test_validators.py
@@ -381,6 +381,7 @@ def test_equal_to_raises(
         u"http://foobar.dk",
         u"http://foobar.dk/",
         u"http://foo-bar.dk/",
+        u"http://foo--bar.dk/",
         u"http://foo_bar.dk/",
         u"http://foobar.dk?query=param",
         u"http://foobar.dk/path?query=param",