Repository: marshmallow-code/marshmallow

Issue:
Remove MarshallingError and UnmarshallingError in favor of a single ValidationError
Currently, `MarshallingError` and `UnmarshallingError` signal that an error should be stored in the `errors` dictionary during marshalling and unmarshalling. 

These exceptions only served a purpose prior to commit https://github.com/marshmallow-code/marshmallow/commit/00a18667b5a7a6a630d07062693e50007eefa5c8, when all exceptions were caught and reraised as (Un)MarshallingErrors. Now, they are only raised when a validation error occurs.

I propose to remove these exception classes in favor of a single `ValidationError`. Doing so would reduce the API and make accessing error messages when using `strict` mode much cleaner.

**Current**:

```python
from marshmallow.exceptions import MarshallingError, UnmarshallingError, ValidationError

schema = MySchema(strict=True)

try:
    schema.dump(some_obj)
except MarshallingError as err:
    if isinstance(err.underlying_error, ValidationError):
        messages = err.underlying_error.messages
    # ...

try:
    schema.load(some_obj)
except UnmarshallingError as err:
    if isinstance(err.underlying_error, ValidationError):
        messages = err.underlying_error.messages
    # ...
```

**Proposed**:

```python
from marshmallow.exceptions import ValidationError

schema = MySchema(strict=True)

try:
    schema.dump(some_obj)
except ValidationError as err:
    messages = err.messages
    # ...

try:
    schema.load(some_obj)
except ValidationError as err:
    messages = err.messages
    # ...
```

This could be implemented in conjunction with https://github.com/marshmallow-code/marshmallow/issues/128 .

**WARNING**: This would be a major breaking change for people using `strict` mode.



Discussion:

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 81840c5e..589c12fe 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,8 +6,9 @@ Changelog
 
 Features:
 
-- *Backwards-incompatible*: When ``many=True``, the errors dictionary returned by ``dump`` and ``load`` will be keyed on the indices of invalid items in the (de)serialized collection (:issue:`75`). Add the ``index_errors`` class Meta option.
+- *Backwards-incompatible*: When ``many=True``, the errors dictionary returned by ``dump`` and ``load`` will be keyed on the indices of invalid items in the (de)serialized collection (:issue:`75`). Add the ``index_errors`` class Meta option to disable this behavior.
 - *Backwards-incompatible*: By default, required fields will raise a ValidationError if the input is ``None`` or the empty string. The ``allow_none`` and ``allow_blank`` parameters can override this behavior.
+- In ``strict`` mode, a ``ValidationError`` is raised. Error messages are accessed via the ``ValidationError's`` ``messages`` attribute (:issue:`128`).
 - Add ``allow_none`` parameter to ``fields.Field``. If ``False`` (the default), validation fails when the field's value is ``None`` (:issue:`76`, :issue:`111`). If ``allow_none`` is ``True``, ``None`` is considered valid and will deserialize to ``None``.
 - Add ``allow_blank`` parameter to ``fields.String`` fields (incl. ``fields.URL``, ``fields.Email``). If ``False`` (the default), validation fails when the field's value is the empty string (:issue:`76`).
 - Schema-level validators can store error messages for multiple fields (:issue:`118`). Thanks :user:`ksesong` for the suggestion.
@@ -23,6 +24,8 @@ Features:
 
 Deprecation/Removals:
 
+- ``MarshallingError`` and ``UnmarshallingError`` error are deprecated in favor of a single ``ValidationError`` (:issue:`160`).
+- Remove ``ForcedError``.
 - Remove support for generator functions that yield validators (:issue:`74`). Plain generators of validators are still supported.
 - The ``Select/Enum`` field is deprecated in favor of using `validate.OneOf` validator (:issue:`135`).
 - Remove legacy, pre-1.0 API (``Schema.data`` and ``Schema.errors`` properties) (:issue:`73`).
diff --git a/docs/custom_fields.rst b/docs/custom_fields.rst
index c0ca8673..78036cc6 100644
--- a/docs/custom_fields.rst
+++ b/docs/custom_fields.rst
@@ -66,8 +66,8 @@ A :class:`Function <marshmallow.fields.Function>` field will take the value of a
 
 .. _adding-context:
 
-Adding Context to Method and Function Fields
---------------------------------------------
+Adding Context to `Method` and `Function` Fields
+------------------------------------------------
 
 A :class:`Function <marshmallow.fields.Function>` or :class:`Method <marshmallow.fields.Method>` field may need information about its environment to know how to serialize a value.
 
diff --git a/docs/extending.rst b/docs/extending.rst
index 1f0fc6f3..78e4b982 100644
--- a/docs/extending.rst
+++ b/docs/extending.rst
@@ -82,7 +82,7 @@ Normally, unspecified field names are ignored by the validator. If you would lik
 Storing Errors on Specific Fields
 +++++++++++++++++++++++++++++++++
 
-If you want to store schema-level validation errors on a specific field, you can pass a field name to the :exc:`ValidationError`.
+If you want to store schema-level validation errors on a specific field, you can pass a field name (or multiple field names) to the :exc:`ValidationError <marshmallow.exceptions.ValidationError>`.
 
 .. code-block:: python
 
@@ -194,7 +194,7 @@ You can register error handlers, validators, and data handlers as optional class
 Extending "class Meta" Options
 --------------------------------
 
-``class Meta`` options are a way to configure and modify a :class:`Schema's <Schema>` behavior. See the :class:`API docs <Schema>` for a listing of available options.
+``class Meta`` options are a way to configure and modify a :class:`Schema's <Schema>` behavior. See the :class:`API docs <Schema.Meta>` for a listing of available options.
 
 You can add custom ``class Meta`` options by subclassing :class:`SchemaOpts`.
 
diff --git a/docs/nesting.rst b/docs/nesting.rst
index 98b81e42..af31e005 100644
--- a/docs/nesting.rst
+++ b/docs/nesting.rst
@@ -24,7 +24,7 @@ Schemas can be nested to represent relationships between objects (e.g. foreign k
             self.title = title
             self.author = author  # A User object
 
-Use a :class:`Nested <marshmallow.fields.Nested>` field to represent the relationship, passing in nested schema class.
+Use a :class:`Nested <marshmallow.fields.Nested>` field to represent the relationship, passing in a nested schema class.
 
 .. code-block:: python
     :emphasize-lines: 10
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index 283c5b70..d657ff82 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -16,13 +16,10 @@ Let's start with a basic user "model".
     import datetime as dt
 
     class User(object):
-        def __init__(self, name, email, age=None):
+        def __init__(self, name, email):
             self.name = name
             self.email = email
             self.created_at = dt.datetime.now()
-            self.friends = []
-            self.employer = None
-            self.age = age
 
         def __repr__(self):
             return '<User(name={self.name!r})>'.format(self=self)
@@ -168,10 +165,10 @@ Validation
 .. code-block:: python
 
     data, errors = UserSchema().load({'email': 'foo'})
-    errors  # => {'email': ['foo is not a valid email address.']}
+    errors  # => {'email': ['"foo" is not a valid email address.']}
     # OR, equivalently
     result = UserSchema().load({'email': 'foo'})
-    result.errors  # => {'email': ['foo is not a valid email address.']}
+    result.errors  # => {'email': ['"foo" is not a valid email address.']}
 
 
 When validating a collection, the errors dictionary will be keyed on the indicies of invalid items.
@@ -207,9 +204,10 @@ You can perform additional validation for a field by passing it a ``validate`` c
     result.errors  # => {'age': ['Validator <lambda>(71.0) is False']}
 
 
-Validation functions either return a boolean or raise a :exc:`ValidationError`. If a :exc:`ValidationError` is raised, its message is stored when validation fails.
+Validation functions either return a boolean or raise a :exc:`ValidationError`. If a :exc:`ValidationError <marshmallow.exceptions.ValidationError>` is raised, its message is stored when validation fails.
 
 .. code-block:: python
+    :emphasize-lines: 7,10,14
 
     from marshmallow import Schema, fields, ValidationError
 
@@ -228,24 +226,30 @@ Validation functions either return a boolean or raise a :exc:`ValidationError`.
 
 .. note::
 
-    If you have multiple validations to perform, you may also pass a collection (list, tuple) or generator of callables to the ``validate`` parameter.
+    If you have multiple validations to perform, you may also pass a collection (list, tuple, generator) of callables.
 
 .. note::
 
     :meth:`Schema.dump` also validates the format of its fields and returns a dictionary of errors. However, the callables passed to ``validate`` are only applied during deserialization.
 
-.. note::
 
-    If you set ``strict=True`` in either the Schema constructor or as a ``class Meta`` option, an error will be raised when invalid data are passed in.
+``strict`` Mode
++++++++++++++++
+
+    If you set ``strict=True`` in either the Schema constructor or as a ``class Meta`` option, an error will be raised when invalid data are passed in. You can access the dictionary of validation errors from the `ValidationError.messages <marshmallow.exceptions.ValidationError.messages>` attribute.
 
     .. code-block:: python
 
-        UserSchema(strict=True).load({'email': 'foo'})
-        # => UnmarshallingError: "foo" is not a valid email address.
+        from marshmallow import ValidationError
 
+        try:
+            UserSchema(strict=True).load({'email': 'foo'})
+        except ValidationError as err:
+            print(err.messages)# => {'email': ['"foo" is not a valid email address.']}
 
-    Alternatively, you can also register a custom error handler function for a schema using the :func:`error_handler <Schema.error_handler>` decorator. See the :ref:`Extending Schemas <extending>` page for more info.
+.. seealso::
 
+    You can register a custom error handler function for a schema using the :func:`error_handler <Schema.error_handler>` decorator. See the :ref:`Extending Schemas <extending>` page for more info.
 
 .. seealso::
 
@@ -310,21 +314,21 @@ By default, `Schemas` will marshal the object attributes that are identical to t
 Specifying Deserialization Keys
 -------------------------------
 
-By default `Schemas` will unmarshal an input dictionary to an output dictionary whose keys are identical to the field names.  However, if you are consuming data that does not exactly match your schema, you can specify additional keys to load values from.
+By default `Schemas` will unmarshal an input dictionary to an output dictionary whose keys are identical to the field names.  However, if you are consuming data that does not exactly match your schema, you can specify additional keys to load values by passing the `load_from` argument.
 
 .. code-block:: python
     :emphasize-lines: 2,3,11,12
 
     class UserSchema(Schema):
         name = fields.String()
-        email = fields.Email(load_from='email_address')
+        email = fields.Email(load_from='emailAddress')
 
     data = {
         'name': 'Mike',
-        'email_address': 'foo@bar.com'
+        'emailAddress': 'foo@bar.com'
     }
-    ser = UserSchema()
-    result, errors = ser.load(data)
+    s = UserSchema()
+    result, errors = s.load(data)
     #{'name': u'Mike',
     # 'email': 'foo@bar.com'}
 
@@ -361,7 +365,8 @@ Note that ``name`` will be automatically formatted as a :class:`String <marshmal
         class UserSchema(Schema):
             uppername = fields.Function(lambda obj: obj.name.upper())
             class Meta:
-                additional = ("name", "email", "created_at")  # No need to include 'uppername'
+                # No need to include 'uppername'
+                additional = ("name", "email", "created_at")
 
 Ordering Output
 ---------------
diff --git a/docs/upgrading.rst b/docs/upgrading.rst
index c18cce82..26d448ff 100644
--- a/docs/upgrading.rst
+++ b/docs/upgrading.rst
@@ -78,11 +78,88 @@ When validating a collection (i.e. when calling ``load`` or ``dump`` with ``many
 
 You can still get the pre-2.0 behavior by setting the ``index_errors`` *class Meta* option to `False`.
 
+Use ``ValidationError`` instead of ``MarshallingError`` and ``UnmarshallingError``
+**********************************************************************************
+
+The :exc:`MarshallingError` and :exc:`UnmarshallingError` exceptions are deprecated in favor of a single :exc:`ValidationError <marshmallow.exceptions.ValidationError>`. Users who have written custom fields or are using ``strict`` mode will need to change their code accordingly.
+
+Custom Fields
+-------------
+
+Custom fields should raise :exc:`ValidationError <marshmallow.exceptions.ValidationError>` in their `_deserialize` and `_serialize` methods when a validation error occurs.
+
+.. code-block:: python
+    :emphasize-lines: 17
+
+    from marshmallow import fields, ValidationError
+    from marshmallow.exceptions import UnmarshallingError
+
+    # In 1.0, an UnmarshallingError was raised
+    class PasswordField(fields.Field):
+
+        def _deserialize(self, val):
+            if not len(val) >= 6:
+                raise UnmarshallingError('Password to short.')
+            return val
+
+    # In 2.0, an UnmarshallingError is raised
+    class PasswordField(fields.Field):
+
+        def _deserialize(self, val):
+            if not len(val) >= 6:
+                raise ValidationError('Password to short.')
+            return val
+
+Handle ``ValidationError`` in strict mode
+-----------------------------------------
+
+When using `strict` mode, you should handle `ValidationErrors` when calling `Schema.dump` and `Schema.load`.
+
+.. code-block:: python
+    :emphasize-lines: 14
+
+    from marshmallow import exceptions as exc
+
+    schema = BandMemberSchema(strict=True)
+
+    # 1.0
+    try:
+        schema.load({'email': 'invalid-email'})
+    except exc.UnmarshallingError as err:
+        # ...
+
+    # 2.0
+    try:
+        schema.load({'email': 'invalid-email'})
+    except exc.ValidationError as err:
+        # ...
+
+
+Accessing error messages in strict mode
+***************************************
+
+In 2.0, `strict` mode was improved so that you can access all error messages for a schema (rather than failing early) by accessing a `ValidationError's` ``messages`` attribute.
+
+.. code-block:: python
+    :emphasize-lines: 6
+
+    schema = BandMemberSchema(strict=True)
+
+    try:
+        result = schema.load({'email': 'invalid'})
+    except ValidationMessage as err:
+        print(err.messages)
+    # {
+    #     'email': ['"invalid" is not a valid email address.'],
+    #     'name': ['Missing data for required field.']
+    # }
+
+
 
 Use ``OneOf`` instead of ``fields.Select``
 ******************************************
 
-The `fields.Select` field was deprecated in favor of the newly-added `OneOf` validator.
+The `fields.Select` field is deprecated in favor of the newly-added `OneOf` validator.
 
 .. code-block:: python
 
@@ -95,6 +172,7 @@ The `fields.Select` field was deprecated in favor of the newly-added `OneOf` val
     # 2.0
     fields.Str(validate=OneOf(['red', 'blue']))
 
+
 Upgrading to 1.2
 ++++++++++++++++
 
diff --git a/marshmallow/exceptions.py b/marshmallow/exceptions.py
index 5d38f7d5..706b28f5 100644
--- a/marshmallow/exceptions.py
+++ b/marshmallow/exceptions.py
@@ -1,87 +1,74 @@
 # -*- coding: utf-8 -*-
 """Exception classes for marshmallow-related errors."""
-from marshmallow.compat import text_type, basestring
+import warnings
+
+from marshmallow.compat import basestring
 
 class MarshmallowError(Exception):
     """Base class for all marshmallow-related errors."""
     pass
 
 
-class _WrappingException(MarshmallowError):
-    """Exception that wraps a different, underlying exception. Used so that
-    an error in serialization or deserialization can be reraised as a
-    :exc:`MarshmallowError <MarshmallowError>`.
-    """
-
-    def __init__(self, underlying_exception, fields=None, field_names=None):
-        if isinstance(underlying_exception, Exception):
-            self.underlying_exception = underlying_exception
-        else:
-            self.underlying_exception = None
-        self.fields = fields
-        self.field_names = field_names
-        super(_WrappingException, self).__init__(
-            text_type(underlying_exception)
-        )
-
-
-class ForcedError(_WrappingException):
-    """Error that always gets raised, even during serialization.
-    Field classes should raise this error if the error should not be stored in
-    the Marshaller's error dictionary and should instead be raised.
-
-    Must be instantiated with an underlying exception.
-
-    Example: ::
-
-        def _serialize(self, value, key, obj):
-            if not isinstace(value, dict):
-                raise ForcedError(ValueError('Value must be a dict.'))
-    """
-    pass
-
-
 class ValidationError(MarshmallowError):
-    """Raised when validation fails on a field.
+    """Raised when validation fails on a field. Validators and custom fields should
+    raise this exception.
 
     :param message: An error message, list of error messages, or dict of
         error messages.
-    :param str field: Field name (or list of field names) to store the error on.
+    :param list field_names: Field names to store the error on.
         If `None`, the error is stored in its default location.
+    :param list fields: `Field` objects to which the error applies.
     """
 
-    def __init__(self, message, field=None):
+    def __init__(self, message, field_names=None, fields=None):
         if not isinstance(message, dict) and not isinstance(message, list):
             messages = [message]
         else:
             messages = message
+        #: String, list, or dictionary of error messages.
+        #: If a `dict`, the keys will be field names and the values will be lists of
+        #: messages.
         self.messages = messages
-        self.field = field
-        if isinstance(field, basestring):
-            self.fields = [field]
-        else:  # field is a list or None
-            self.fields = field
+        #: List of field objects which failed validation.
+        self.fields = fields
+        if isinstance(field_names, basestring):
+            #: List of field_names which failed validation.
+            self.field_names = [field_names]
+        else:  # fields is a list or None
+            self.field_names = field_names or []
         MarshmallowError.__init__(self, message)
 
 
-class RegistryError(ForcedError, NameError):
+class RegistryError(NameError):
     """Raised when an invalid operation is performed on the serializer
     class registry.
     """
     pass
 
 
-class MarshallingError(_WrappingException):
+class MarshallingError(ValidationError):
     """Raised in case of a marshalling error. If raised during serialization,
     the error is caught and the error message is stored in an ``errors``
     dictionary (unless ``strict`` mode is turned on).
+
+    .. deprecated:: 2.0.0
+        Use :exc:`ValidationError` instead.
     """
-    pass
+    def __init__(self, *args, **kwargs):
+        warnings.warn('MarshallingError is deprecated. Raise a ValidationError instead',
+                      category=DeprecationWarning)
+        super(MarshallingError, self).__init__(*args, **kwargs)
 
 
-class UnmarshallingError(_WrappingException):
+class UnmarshallingError(ValidationError):
     """Raised when invalid data are passed to a deserialization function. If
     raised during deserialization, the error is caught and the error message
     is stored in an ``errors`` dictionary.
+
+    .. deprecated:: 2.0.0
+        Use :exc:`ValidationError` instead.
     """
-    pass
+    def __init__(self, *args, **kwargs):
+        warnings.warn('UnmarshallingError is deprecated. Raise a ValidationError instead',
+                      category=DeprecationWarning)
+        super(UnmarshallingError, self).__init__(*args, **kwargs)
diff --git a/marshmallow/fields.py b/marshmallow/fields.py
index 182b832b..e1f64618 100755
--- a/marshmallow/fields.py
+++ b/marshmallow/fields.py
@@ -13,13 +13,7 @@ from marshmallow import validate, utils, class_registry
 from marshmallow.base import FieldABC, SchemaABC
 from marshmallow.marshalling import null, missing
 from marshmallow.compat import text_type, basestring
-from marshmallow.exceptions import (
-    MarshallingError,
-    UnmarshallingError,
-    ForcedError,
-    ValidationError,
-)
-
+from marshmallow.exceptions import ValidationError
 
 __all__ = [
     'Field',
@@ -72,8 +66,8 @@ class Field(FieldABC):
     :param callable validate: Validator or collection of validators that are called
         during deserialization. Validator takes a field's input value as
         its only parameter and returns a boolean.
-        If it returns `False`, an :exc:`UnmarshallingError` is raised.
-    :param required: Raise an :exc:`UnmarshallingError` if the field value
+        If it returns `False`, an :exc:`ValidationError` is raised.
+    :param required: Raise an :exc:`ValidationError` if the field value
         is not supplied during deserialization. If not a `bool`(e.g. a `str`),
         the provided value will be used as the message of the
         :exc:`ValidationError` instead of the default message.
@@ -186,27 +180,6 @@ class Field(FieldABC):
         if errors:
             raise ValidationError(errors)
 
-    def _call_and_reraise(self, func, exception_class):
-        """Utility method to invoke a function and raise ``exception_class`` if an error
-        occurs.
-
-        :param callable func: Function to call. Must take no arguments.
-        :param Exception exception_class: Type of exception to raise when an error occurs.
-        """
-        try:
-            return func()
-        # TypeErrors should be raised if fields are not declared as instances
-        except TypeError:
-            raise
-        # Raise ForcedErrors
-        except ForcedError as err:
-            if err.underlying_exception:
-                raise err.underlying_exception
-            else:
-                raise err
-        except ValidationError as err:
-            raise exception_class(err)
-
     def _validate_missing(self, value):
         """Validate missing values. Raise a :exc:`ValidationError` if
         `value` should be considered missing.
@@ -231,7 +204,7 @@ class Field(FieldABC):
         :param str attr: The attibute or key to get from the object.
         :param str obj: The object to pull the key from.
         :param callable accessor: Function used to pull values from ``obj``.
-        :raise MarshallingError: In case of formatting problem
+        :raise ValidationError: In case of formatting problem
         """
         value = self.get_value(attr, obj, accessor=accessor)
         if value is None and self._CHECK_ATTRIBUTE:
@@ -240,25 +213,22 @@ class Field(FieldABC):
                     return self.default()
                 else:
                     return self.default
-        func = lambda: self._serialize(value, attr, obj)
-        return self._call_and_reraise(func, MarshallingError)
+        return self._serialize(value, attr, obj)
 
     def deserialize(self, value):
         """Deserialize ``value``.
 
-        :raise UnmarshallingError: If an invalid value is passed or if a required value
+        :raise ValidationError: If an invalid value is passed or if a required value
             is missing.
         """
         # Validate required fields, deserialize, then validate
         # deserialized value
-        def do_deserialization():
-            self._validate_missing(value)
-            if getattr(self, 'allow_none', False) is True and value is None:
-                return None
-            output = self._deserialize(value)
-            self._validate(output)
-            return output
-        return self._call_and_reraise(do_deserialization, UnmarshallingError)
+        self._validate_missing(value)
+        if getattr(self, 'allow_none', False) is True and value is None:
+            return None
+        output = self._deserialize(value)
+        self._validate(output)
+        return output
 
     # Methods for concrete classes to override.
 
@@ -277,14 +247,14 @@ class Field(FieldABC):
         :param value: The value to be serialized.
         :param str attr: The attribute or key on the object to be serialized.
         :param object obj: The object the value was pulled from.
-        :raise MarshallingError: In case of formatting or validation failure.
+        :raise ValidationError: In case of formatting or validation failure.
         """
         return value
 
     def _deserialize(self, value):
         """Deserialize value. Concrete :class:`Field` classes should implement this method.
 
-        :raise UnmarshallingError: In case of formatting or validation failure.
+        :raise ValidationError: In case of formatting or validation failure.
         """
         return value
 
@@ -362,9 +332,8 @@ class Nested(Field):
                     self.__schema = schema_class(many=self.many,
                             only=only, exclude=self.exclude)
             else:
-                raise ForcedError(ValueError('Nested fields must be passed a '
-                                    'Schema, not {0}.'
-                                    .format(self.nested.__class__)))
+                raise ValueError('Nested fields must be passed a '
+                                 'Schema, not {0}.'.format(self.nested.__class__))
         self.__schema.ordered = getattr(self.parent, 'ordered', False)
         # Inherit context from parent
         self.__schema.context.update(getattr(self.parent, 'context', {}))
@@ -496,7 +465,7 @@ class UUID(String):
 
     def _deserialize(self, value):
         msg = 'Could not deserialize {0!r} to a UUID object.'.format(value)
-        err = UnmarshallingError(getattr(self, 'error', None) or msg)
+        err = ValidationError(getattr(self, 'error', None) or msg)
         try:
             return uuid.UUID(value)
         except (ValueError, AttributeError):
@@ -521,14 +490,14 @@ class Number(Field):
         """Return the number value for value, given this field's `num_type`."""
         return self.num_type(value)
 
-    def _validated(self, value, exception_class):
+    def _validated(self, value):
         """Format the value or raise ``exception_class`` if an error occurs."""
         if value is None:
             return self.default
         try:
             return self._format_num(value)
         except (TypeError, ValueError, decimal.InvalidOperation) as err:
-            raise exception_class(getattr(self, 'error', None) or err)
+            raise ValidationError(getattr(self, 'error', None) or text_type(err))
 
     def serialize(self, attr, obj, accessor=None):
         """Pulls the value for the given key from the object and returns the
@@ -540,10 +509,10 @@ class Number(Field):
         return str(ret) if self.as_string else ret
 
     def _serialize(self, value, attr, obj):
-        return self._validated(value, MarshallingError)
+        return self._validated(value)
 
     def _deserialize(self, value):
-        return self._validated(value, UnmarshallingError)
+        return self._validated(value)
 
 
 class Integer(Number):
@@ -624,14 +593,14 @@ class Boolean(Field):
         try:
             value_str = text_type(value)
         except TypeError as error:
-            raise UnmarshallingError(error)
+            raise ValidationError(text_type(error))
         if value_str in self.falsy:
             return False
         elif self.truthy:
             if value_str in self.truthy:
                 return True
             else:
-                raise UnmarshallingError(
+                raise ValidationError(
                     '{0!r} is not in {1} nor {2}'.format(
                         value_str, self.truthy, self.falsy
                     ))
@@ -663,7 +632,7 @@ class FormattedString(Field):
             data = utils.to_marshallable_type(obj)
             return self.src_str.format(**data)
         except (TypeError, IndexError) as error:
-            raise MarshallingError(getattr(self, 'error', None) or error)
+            raise ValidationError(getattr(self, 'error', None) or error)
 
 
 class Float(Number):
@@ -695,20 +664,20 @@ class Arbitrary(Number):
         )
         super(Arbitrary, self).__init__(default=default, attribute=attribute, **kwargs)
 
-    def _validated(self, value, exception_class):
+    def _validated(self, value):
         """Format ``value`` or raise ``exception_class`` if an error occurs."""
         try:
             if value is None:
                 return self.default
             return text_type(utils.float_to_decimal(float(value)))
         except ValueError as ve:
-            raise exception_class(ve)
+            raise ValidationError(text_type(ve))
 
     def _serialize(self, value, attr, obj):
-        return self._validated(value, MarshallingError)
+        return self._validated(value)
 
     def _deserialize(self, value):
-        return self._validated(value, UnmarshallingError)
+        return self._validated(value)
 
 
 class DateTime(Field):
@@ -758,13 +727,13 @@ class DateTime(Field):
                 try:
                     return format_func(value, localtime=self.localtime)
                 except (AttributeError, ValueError) as err:
-                    raise MarshallingError(getattr(self, 'error', None) or err)
+                    raise ValidationError(getattr(self, 'error', None) or text_type(err))
             else:
                 return value.strftime(self.dateformat)
 
     def _deserialize(self, value):
         msg = 'Could not deserialize {0!r} to a datetime object.'.format(value)
-        err = UnmarshallingError(getattr(self, 'error', None) or msg)
+        err = ValidationError(getattr(self, 'error', None) or msg)
         if not value:  # Falsy values, e.g. '', None, [] are not valid
             raise err
         self.dateformat = self.dateformat or self.DEFAULT_FORMAT
@@ -806,7 +775,7 @@ class Time(Field):
             ret = value.isoformat()
         except AttributeError:
             msg = '{0!r} cannot be formatted as a time.'.format(value)
-            raise MarshallingError(getattr(self, 'error', None) or msg)
+            raise ValidationError(getattr(self, 'error', None) or msg)
         if value.microsecond:
             return ret[:12]
         return ret
@@ -814,7 +783,7 @@ class Time(Field):
     def _deserialize(self, value):
         """Deserialize an ISO8601-formatted time to a :class:`datetime.time` object."""
         msg = 'Could not deserialize {0!r} to a time object.'.format(value)
-        err = UnmarshallingError(getattr(self, 'error', None) or msg)
+        err = ValidationError(getattr(self, 'error', None) or msg)
         if not value:   # falsy values are invalid
             raise err
         try:
@@ -833,7 +802,7 @@ class Date(Field):
             return value.isoformat()
         except AttributeError:
             msg = '{0} cannot be formatted as a date.'.format(repr(value))
-            raise MarshallingError(getattr(self, 'error', None) or msg)
+            raise ValidationError(getattr(self, 'error', None) or msg)
         return value
 
     def _deserialize(self, value):
@@ -841,7 +810,7 @@ class Date(Field):
         :class:`datetime.date` object.
         """
         msg = 'Could not deserialize {0!r} to a date object.'.format(value)
-        err = UnmarshallingError(getattr(self, 'error', None) or msg)
+        err = ValidationError(getattr(self, 'error', None) or msg)
         if not value:  # falsy values are invalid
             raise err
         try:
@@ -893,14 +862,14 @@ class TimeDelta(Field):
                     return seconds * 10**6 + value.microseconds  # flake8: noqa
         except AttributeError:
             msg = '{0!r} cannot be formatted as a timedelta.'.format(value)
-            raise MarshallingError(getattr(self, 'error', None) or msg)
+            raise ValidationError(getattr(self, 'error', None) or msg)
 
     def _deserialize(self, value):
         try:
             value = int(value)
         except (TypeError, ValueError):
             msg = '{0!r} cannot be interpreted as a valid period of time.'.format(value)
-            raise UnmarshallingError(getattr(self, 'error', None) or msg)
+            raise ValidationError(getattr(self, 'error', None) or msg)
 
         kwargs = {self.precision: value}
 
@@ -908,7 +877,7 @@ class TimeDelta(Field):
             return dt.timedelta(**kwargs)
         except OverflowError:
             msg = '{0!r} cannot be interpreted as a valid period of time.'.format(value)
-            raise UnmarshallingError(getattr(self, 'error', None) or msg)
+            raise ValidationError(getattr(self, 'error', None) or msg)
 
 
 class Fixed(Number):
@@ -930,21 +899,15 @@ class Fixed(Number):
                             *args, **kwargs)
         self.precision = decimal.Decimal('0.' + '0' * (decimals - 1) + '1')
 
-    def _serialize(self, value, attr, obj):
-        return self._validated(value, MarshallingError)
-
-    def _deserialize(self, value):
-        return self._validated(value, UnmarshallingError)
-
-    def _validated(self, value, exception_class):
+    def _validated(self, value):
         if value is None:
             value = self.default
         try:
             dvalue = utils.float_to_decimal(float(value))
         except (TypeError, ValueError) as err:
-            raise exception_class(getattr(self, 'error', None) or err)
+            raise ValidationError(getattr(self, 'error', None) or text_type(err))
         if not dvalue.is_normal() and dvalue != utils.ZERO_DECIMAL:
-            raise exception_class(
+            raise ValidationError(
                 getattr(self, 'error', None) or 'Invalid Fixed precision number.'
             )
         return utils.decimal_to_fixed(dvalue, self.precision)
@@ -1067,7 +1030,7 @@ class Method(Field):
             if len(utils.get_func_args(method)) > 2:
                 if self.parent.context is None:
                     msg = 'No context available for Method field {0!r}'.format(attr)
-                    raise MarshallingError(msg)
+                    raise ValidationError(msg)
                 return method(obj, self.parent.context)
             else:
                 return method(obj)
@@ -1111,7 +1074,7 @@ class Function(Field):
             if len(utils.get_func_args(self.func)) > 1:
                 if self.parent.context is None:
                     msg = 'No context available for Function field {0!r}'.format(attr)
-                    raise MarshallingError(msg)
+                    raise ValidationError(msg)
                 return self.func(obj, self.parent.context)
             else:
                 return self.func(obj)
@@ -1135,7 +1098,7 @@ class Select(Field):
     :param str error: Error message stored upon validation failure.
     :param kwargs: The same keyword arguments that :class:`Fixed` receives.
 
-    :raise: MarshallingError if attribute's value is not one of the given choices.
+    :raise: ValidationError if attribute's value is not one of the given choices.
     """
     def __init__(self, choices, default=None, attribute=None, error=None, **kwargs):
         warnings.warn(
@@ -1146,19 +1109,19 @@ class Select(Field):
         self.choices = choices
         return super(Select, self).__init__(default, attribute, error, **kwargs)
 
-    def _validated(self, value, exception_class):
+    def _validated(self, value):
         if value not in self.choices:
-            raise exception_class(
+            raise ValidationError(
                 getattr(self, 'error', None) or
                 "{0!r} is not a valid choice for this field.".format(value)
             )
         return value
 
     def _serialize(self, value, attr, obj):
-        return self._validated(value, MarshallingError)
+        return self._validated(value)
 
     def _deserialize(self, value):
-        return self._validated(value, UnmarshallingError)
+        return self._validated(value)
 
 
 class QuerySelect(Field):
@@ -1223,7 +1186,7 @@ class QuerySelect(Field):
                 return value
 
         error = getattr(self, 'error', None) or 'Invalid object.'
-        raise MarshallingError(error)
+        raise ValidationError(error)
 
     def _deserialize(self, value):
         for key, result in self.pairs():
@@ -1231,7 +1194,7 @@ class QuerySelect(Field):
                 return result
 
         error = getattr(self, 'error', None) or 'Invalid key.'
-        raise UnmarshallingError(error)
+        raise ValidationError(error)
 
 
 class QuerySelectList(QuerySelect):
@@ -1261,7 +1224,7 @@ class QuerySelectList(QuerySelect):
                 keys.remove(item)
             except ValueError:
                 error = getattr(self, 'error', None) or 'Invalid objects.'
-                raise MarshallingError(error)
+                raise ValidationError(error)
 
         return items
 
@@ -1277,7 +1240,7 @@ class QuerySelectList(QuerySelect):
                 index = keys.index(val)
             except ValueError:
                 error = getattr(self, 'error', None) or 'Invalid keys.'
-                raise UnmarshallingError(error)
+                raise ValidationError(error)
             else:
                 del keys[index]
                 items.append(results.pop(index))
diff --git a/marshmallow/marshalling.py b/marshmallow/marshalling.py
index a1f47cb4..ffcb3a67 100644
--- a/marshmallow/marshalling.py
+++ b/marshmallow/marshalling.py
@@ -14,8 +14,6 @@ from marshmallow import base, utils
 from marshmallow.compat import text_type, iteritems
 from marshmallow.exceptions import (
     ValidationError,
-    MarshallingError,
-    UnmarshallingError,
 )
 
 __all__ = [
@@ -42,10 +40,7 @@ class _Missing(_Null):
         return '<marshmallow.marshalling.missing>'
 
 
-# Singleton that represents an empty value. Used as the default for Nested
-# fields so that `Field._call_with_validation` is invoked, even when the
-# object to serialize has the nested attribute set to None. Therefore,
-# `RegistryErrors` are properly raised.
+# Singleton that represents an empty value.
 null = _Null()
 
 # Singleton value that indicates that a field's value is missing from input
@@ -54,63 +49,64 @@ null = _Null()
 missing = _Missing()
 
 
-def _call_and_store(getter_func, data, field_name, field_obj, errors_dict,
-               exception_class, strict=False, index=None):
-    """Helper method for DRYing up logic in the :meth:`Marshaller.serialize` and
-    :meth:`Unmarshaller.deserialize` methods. Call ``getter_func`` with ``data`` as its
-    argument, and store any errors of type ``exception_class`` in ``error_dict``.
-
-    :param callable getter_func: Function for getting the serialized/deserialized
-        value from ``data``.
-    :param data: The data passed to ``getter_func``.
-    :param str field_name: Field name.
-    :param FieldABC field_obj: Field object that performs the
-        serialization/deserialization behavior.
-    :param dict errors_dict: Dictionary to store errors on.
-    :param type exception_class: Exception class that will be caught during
-        serialization/deserialization. Errors of this type will be stored
-        in ``errors_dict``.
-    :param int index: Index of the item being validated, if validating a collection,
-        otherwise `None`.
-    """
-    try:
-        value = getter_func(data)
-    except exception_class as err:  # Store errors
-        if strict:
-            err.field = field_obj
-            err.field_name = field_name
-            raise err
-        # Warning: Mutation!
-        if index is not None:
-            errors = {}
-            errors_dict[index] = errors
-        else:
-            errors = errors_dict
-        # Warning: Mutation!
-        if (hasattr(err, 'underlying_exception') and
-                isinstance(err.underlying_exception, ValidationError)):
-            validation_error = err.underlying_exception
-            if isinstance(validation_error.messages, dict):
-                errors[field_name] = validation_error.messages
+class ErrorStore(object):
+
+    def __init__(self):
+        #: Dictionary of errors stored during serialization
+        self.errors = {}
+        #: List of `Field` objects which have validation errors
+        self.error_fields = []
+        #: List of field_names which have validation errors
+        self.error_field_names = []
+        #: True while (de)serializing a collection
+        self._pending = False
+
+    def reset_errors(self):
+        self.errors = {}
+        self.error_field_names = []
+        self.error_fields = []
+
+    def call_and_store(self, getter_func, data, field_name, field_obj, index=None):
+        """Call ``getter_func`` with ``data`` as its argument, and store any `ValidationErrors`.
+
+        :param callable getter_func: Function for getting the serialized/deserialized
+            value from ``data``.
+        :param data: The data passed to ``getter_func``.
+        :param str field_name: Field name.
+        :param FieldABC field_obj: Field object that performs the
+            serialization/deserialization behavior.
+        :param int index: Index of the item being validated, if validating a collection,
+            otherwise `None`.
+        """
+        try:
+            value = getter_func(data)
+        except ValidationError as err:  # Store validation errors
+            self.error_fields.append(field_obj)
+            self.error_field_names.append(field_name)
+            if index is not None:
+                errors = {}
+                self.errors[index] = errors
             else:
-                errors.setdefault(field_name, []).extend(validation_error.messages)
-        else:
-            errors.setdefault(field_name, []).append(text_type(err))
-        value = None
-    except TypeError:
-        # field declared as a class, not an instance
-        if (isinstance(field_obj, type) and
-                issubclass(field_obj, base.FieldABC)):
-            msg = ('Field for "{0}" must be declared as a '
-                            'Field instance, not a class. '
-                            'Did you mean "fields.{1}()"?'
-                            .format(field_name, field_obj.__name__))
-            raise TypeError(msg)
-        raise
-    return value
-
-
-class Marshaller(object):
+                errors = self.errors
+            # Warning: Mutation!
+            if isinstance(err.messages, dict):
+                errors[field_name] = err.messages
+            else:
+                errors.setdefault(field_name, []).extend(err.messages)
+            value = None
+        except TypeError:
+            # field declared as a class, not an instance
+            if (isinstance(field_obj, type) and
+                    issubclass(field_obj, base.FieldABC)):
+                msg = ('Field for "{0}" must be declared as a '
+                                'Field instance, not a class. '
+                                'Did you mean "fields.{1}()"?'
+                                .format(field_name, field_obj.__name__))
+                raise TypeError(msg)
+            raise
+        return value
+
+class Marshaller(ErrorStore):
     """Callable class responsible for serializing data and storing errors.
 
     :param str prefix: Optional prefix that will be prepended to all the
@@ -118,10 +114,7 @@ class Marshaller(object):
     """
     def __init__(self, prefix=''):
         self.prefix = prefix
-        #: Dictionary of errors stored during serialization
-        self.errors = {}
-        #: True while serializing a collection
-        self.__pending = False
+        ErrorStore.__init__(self)
 
     def serialize(self, obj, fields_dict, many=False, strict=False, skip_missing=False,
                   accessor=None, dict_class=dict, index_errors=True, index=None):
@@ -147,29 +140,26 @@ class Marshaller(object):
             Renamed from ``marshal``.
         """
         # Reset errors dict if not serializing a collection
-        if not self.__pending:
-            self.errors = {}
+        if not self._pending:
+            self.reset_errors()
         if many and obj is not None:
-            self.__pending = True
+            self._pending = True
             ret = [self.serialize(d, fields_dict, many=False, strict=strict,
                                     dict_class=dict_class, accessor=accessor,
                                     skip_missing=skip_missing,
                                     index=idx, index_errors=index_errors)
                     for idx, d in enumerate(obj)]
-            self.__pending = False
+            self._pending = False
             return ret
         items = []
         for attr_name, field_obj in iteritems(fields_dict):
             key = ''.join([self.prefix, attr_name])
             getter = lambda d: field_obj.serialize(attr_name, d, accessor=accessor)
-            value = _call_and_store(
+            value = self.call_and_store(
                 getter_func=getter,
                 data=obj,
                 field_name=key,
                 field_obj=field_obj,
-                errors_dict=self.errors,
-                exception_class=MarshallingError,
-                strict=strict,
                 index=(index if index_errors else None)
             )
             skip_conds = (
@@ -180,22 +170,23 @@ class Marshaller(object):
             if any(skip_conds):
                 continue
             items.append((key, value))
+        if self.errors and strict:
+            raise ValidationError(
+                self.errors,
+                field_names=self.error_field_names,
+                fields=self.error_fields
+            )
         return dict_class(items)
 
     # Make an instance callable
     __call__ = serialize
 
 
-class Unmarshaller(object):
+class Unmarshaller(ErrorStore):
     """Callable class responsible for deserializing data and storing errors.
 
     .. versionadded:: 1.0.0
     """
-    def __init__(self):
-        #: Dictionary of errors stored during deserialization
-        self.errors = {}
-        #: True while deserializing a collection
-        self.__pending = False
 
     def _validate(self, validators, output, raw_data, fields_dict, strict=False):
         """Perform schema-level validation. Stores errors if ``strict`` is `False`.
@@ -214,15 +205,12 @@ class Unmarshaller(object):
                     ))
             except ValidationError as err:
                 # Store or reraise errors
-                if err.fields:
-                    field_names = err.fields
+                if err.field_names:
+                    field_names = err.field_names
                     field_objs = [fields_dict[each] for each in field_names]
                 else:
                     field_names = ['_schema']
                     field_objs = []
-                if strict:
-                    raise UnmarshallingError(err, fields=field_objs,
-                                             field_names=field_names)
                 for field_name in field_names:
                     if isinstance(err.messages, (list, tuple)):
                         # self.errors[field_name] may be a dict if schemas are nested
@@ -236,6 +224,12 @@ class Unmarshaller(object):
                         self.errors.setdefault(field_name, []).append(err.messages)
                     else:
                         self.errors.setdefault(field_name, []).append(text_type(err))
+                if strict:
+                    raise ValidationError(
+                        self.errors,
+                        fields=field_objs,
+                        field_names=field_names
+                    )
         return output
 
     def deserialize(self, data, fields_dict, many=False, validators=None,
@@ -261,16 +255,16 @@ class Unmarshaller(object):
         :return: A dictionary of the deserialized data.
         """
         # Reset errors if not deserializing a collection
-        if not self.__pending:
-            self.errors = {}
+        if not self._pending:
+            self.reset_errors()
         if many and data is not None:
-            self.__pending = True
+            self._pending = True
             ret = [self.deserialize(d, fields_dict, many=False,
                         validators=validators, preprocess=preprocess,
                         postprocess=postprocess, strict=strict, dict_class=dict_class,
                         index=idx, index_errors=index_errors)
                     for idx, d in enumerate(data)]
-            self.__pending = False
+            self._pending = False
             return ret
         raw_data = data
         if data is not None:
@@ -287,14 +281,11 @@ class Unmarshaller(object):
                     raw_value = _miss() if callable(_miss) else _miss
                 if raw_value is missing and not field_obj.required:
                     continue
-                value = _call_and_store(
+                value = self.call_and_store(
                     getter_func=field_obj.deserialize,
                     data=raw_value,
                     field_name=key,
                     field_obj=field_obj,
-                    errors_dict=self.errors,
-                    exception_class=UnmarshallingError,
-                    strict=strict,
                     index=(index if index_errors else None)
                 )
                 if raw_value is not missing:
@@ -311,6 +302,12 @@ class Unmarshaller(object):
             validators = validators or []
             ret = self._validate(validators, ret, raw_data, fields_dict=fields_dict,
                                  strict=strict)
+        if self.errors and strict:
+            raise ValidationError(
+                self.errors,
+                field_names=self.error_field_names,
+                fields=self.error_fields
+            )
         if postprocess:
             postprocess = postprocess or []
             for func in postprocess:


Test patch:
diff --git a/tests/base.py b/tests/base.py
index 55980f5b..f4beca08 100644
--- a/tests/base.py
+++ b/tests/base.py
@@ -6,7 +6,8 @@ import uuid
 import pytz
 
 from marshmallow import Schema, fields
-from marshmallow.exceptions import MarshallingError
+from marshmallow.compat import text_type
+from marshmallow.exceptions import ValidationError
 
 central = pytz.timezone("US/Central")
 
@@ -160,7 +161,7 @@ class UserSchema(Schema):
         try:
             return obj.age > 80
         except TypeError as te:
-            raise MarshallingError(te)
+            raise ValidationError(text_type(te))
 
     def make_object(self, data):
         return User(**data)
@@ -181,7 +182,7 @@ class UserMetaSchema(Schema):
         try:
             return obj.age > 80
         except TypeError as te:
-            raise MarshallingError(te)
+            raise ValidationError(te)
 
     class Meta:
         fields = ('name', 'age', 'created', 'updated', 'id', 'homepage',
diff --git a/tests/test_deserialization.py b/tests/test_deserialization.py
index 731fb438..99744563 100644
--- a/tests/test_deserialization.py
+++ b/tests/test_deserialization.py
@@ -6,7 +6,7 @@ import decimal
 import pytest
 
 from marshmallow import fields, utils, Schema
-from marshmallow.exceptions import UnmarshallingError, ValidationError
+from marshmallow.exceptions import ValidationError
 from marshmallow.compat import text_type, basestring
 
 from tests.base import (
@@ -42,7 +42,7 @@ class TestDeserializingNone:
             field = FieldClass(choices=['foo', 'bar'])
         else:
             field = FieldClass()
-        with pytest.raises(UnmarshallingError) as excinfo:
+        with pytest.raises(ValidationError) as excinfo:
             field.deserialize(None)
         assert 'Field may not be null.' in str(excinfo)
 
@@ -65,15 +65,15 @@ class TestFieldDeserialization:
     ])
     def test_invalid_float_field_deserialization(self, in_val):
         field = fields.Float()
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize(in_val)
 
     def test_integer_field_deserialization(self):
         field = fields.Integer()
         assert field.deserialize('42') == 42
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize('42.0')
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize('bad')
 
     def test_decimal_field_deserialization(self):
@@ -90,9 +90,9 @@ class TestFieldDeserialization:
         assert field.deserialize(m2) == decimal.Decimal('12.355')
         assert isinstance(field.deserialize(m3), decimal.Decimal)
         assert field.deserialize(m3) == decimal.Decimal(1)
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize(m4)
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize(m5)
 
     def test_decimal_field_with_places(self):
@@ -109,9 +109,9 @@ class TestFieldDeserialization:
         assert field.deserialize(m2) == decimal.Decimal('12.4')
         assert isinstance(field.deserialize(m3), decimal.Decimal)
         assert field.deserialize(m3) == decimal.Decimal(1)
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize(m4)
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize(m5)
 
     def test_decimal_field_with_places_and_rounding(self):
@@ -128,9 +128,9 @@ class TestFieldDeserialization:
         assert field.deserialize(m2) == decimal.Decimal('12.3')
         assert isinstance(field.deserialize(m3), decimal.Decimal)
         assert field.deserialize(m3) == decimal.Decimal(1)
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize(m4)
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize(m5)
 
     def test_decimal_field_deserialization_string(self):
@@ -147,9 +147,9 @@ class TestFieldDeserialization:
         assert field.deserialize(m2) == decimal.Decimal('12.355')
         assert isinstance(field.deserialize(m3), decimal.Decimal)
         assert field.deserialize(m3) == decimal.Decimal(1)
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize(m4)
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize(m5)
 
     def test_string_field_deserialization(self):
@@ -187,7 +187,7 @@ class TestFieldDeserialization:
         class MyBoolean(fields.Boolean):
             truthy = set(['yep'])
         field = MyBoolean()
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize(in_val)
 
     def test_arbitrary_field_deserialization(self):
@@ -204,7 +204,7 @@ class TestFieldDeserialization:
     ])
     def test_invalid_datetime_deserialization(self, in_value):
         field = fields.DateTime()
-        with pytest.raises(UnmarshallingError) as excinfo:
+        with pytest.raises(ValidationError) as excinfo:
             field.deserialize(in_value)
         msg = 'Could not deserialize {0!r} to a datetime object.'.format(in_value)
         assert msg in str(excinfo)
@@ -255,7 +255,7 @@ class TestFieldDeserialization:
     ])
     def test_invalid_time_field_deserialization(self, in_data):
         field = fields.Time()
-        with pytest.raises(UnmarshallingError) as excinfo:
+        with pytest.raises(ValidationError) as excinfo:
             field.deserialize(in_data)
         msg = 'Could not deserialize {0!r} to a time object.'.format(in_data)
         assert msg in str(excinfo)
@@ -268,7 +268,7 @@ class TestFieldDeserialization:
 
     def test_fixed_field_deserialize_invalid_value(self):
         field = fields.Fixed(decimals=3)
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize('badvalue')
 
     def test_timedelta_field_deserialization(self):
@@ -322,7 +322,7 @@ class TestFieldDeserialization:
     ])
     def test_invalid_timedelta_field_deserialization(self, in_value):
         field = fields.TimeDelta(fields.TimeDelta.DAYS)
-        with pytest.raises(UnmarshallingError) as excinfo:
+        with pytest.raises(ValidationError) as excinfo:
             field.deserialize(in_value)
         msg = '{0!r} cannot be interpreted as a valid period of time.'.format(in_value)
         assert msg in str(excinfo)
@@ -343,7 +343,7 @@ class TestFieldDeserialization:
     ])
     def test_invalid_date_field_deserialization(self, in_value):
         field = fields.Date()
-        with pytest.raises(UnmarshallingError) as excinfo:
+        with pytest.raises(ValidationError) as excinfo:
             field.deserialize(in_value)
         msg = 'Could not deserialize {0!r} to a date object.'.format(in_value)
         assert msg in str(excinfo)
@@ -355,10 +355,10 @@ class TestFieldDeserialization:
     def test_url_field_deserialization(self):
         field = fields.Url()
         assert field.deserialize('https://duckduckgo.com') == 'https://duckduckgo.com'
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize('badurl')
         # Relative URLS not allowed by default
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize('/foo/bar')
 
     def test_relative_url_field_deserialization(self):
@@ -368,7 +368,7 @@ class TestFieldDeserialization:
     def test_email_field_deserialization(self):
         field = fields.Email()
         assert field.deserialize('foo@bar.com') == 'foo@bar.com'
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize('invalidemail')
 
     def test_function_field_deserialization_is_noop_by_default(self):
@@ -397,7 +397,7 @@ class TestFieldDeserialization:
     ])
     def test_invalid_uuid_deserialization(self, in_value):
         field = fields.UUID()
-        with pytest.raises(UnmarshallingError) as excinfo:
+        with pytest.raises(ValidationError) as excinfo:
             field.deserialize(in_value)
         msg = 'Could not deserialize {0!r} to a UUID object.'.format(in_value)
         assert msg in str(excinfo)
@@ -441,7 +441,7 @@ class TestFieldDeserialization:
     def test_enum_field_deserialization(self):
         field = fields.Enum(['red', 'blue'])
         assert field.deserialize('red') == 'red'
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize('notvalid')
 
     def test_query_select_field_func_key_deserialization(self):
@@ -451,9 +451,9 @@ class TestFieldDeserialization:
         assert field.deserialize('bar a') == DummyModel('a')
         assert field.deserialize('bar b') == DummyModel('b')
         assert field.deserialize('bar c') == DummyModel('c')
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize('bar d')
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize('c')
         assert list(field.keys()) == ['bar ' + ch for ch in 'abc']
         assert list(field.results()) == [DummyModel(ch) for ch in 'abc']
@@ -469,9 +469,9 @@ class TestFieldDeserialization:
         assert field.deserialize('a') == DummyModel('a')
         assert field.deserialize('b') == DummyModel('b')
         assert field.deserialize('c') == DummyModel('c')
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize('d')
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize('bar d')
         assert list(field.keys()) == [ch for ch in 'abc']
         assert list(field.results()) == [DummyModel(ch) for ch in 'abc']
@@ -489,9 +489,9 @@ class TestFieldDeserialization:
         assert field.deserialize(['bar d', 'bar e', 'bar e']) == \
                [DummyModel('d'), DummyModel('e'), DummyModel('e')]
         assert field.deserialize([]) == []
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize(['a', 'b', 'f'])
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize(['a', 'b', 'b'])
 
     def test_query_select_list_field_string_key_deserialization(self):
@@ -503,16 +503,16 @@ class TestFieldDeserialization:
         assert field.deserialize(['d', 'e', 'e']) == \
                [DummyModel('d'), DummyModel('e'), DummyModel('e')]
         assert field.deserialize([]) == []
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize(['a', 'b', 'f'])
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize(['a', 'b', 'b'])
 
     def test_fixed_list_field_deserialization(self):
         field = fields.List(fields.Fixed(3))
         nums = (1, 2, 3)
         assert field.deserialize(nums) == ['1.000', '2.000', '3.000']
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize((1, 2, 'invalid'))
 
     def test_datetime_list_field_deserialization(self):
@@ -533,16 +533,16 @@ class TestFieldDeserialization:
 
     def test_list_field_deserialize_invalid_value(self):
         field = fields.List(fields.DateTime)
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize('badvalue')
 
     def test_field_deserialization_with_user_validator_function(self):
         field = fields.String(validate=lambda s: s.lower() == 'valid')
         assert field.deserialize('Valid') == 'Valid'
-        with pytest.raises(UnmarshallingError) as excinfo:
+        with pytest.raises(ValidationError) as excinfo:
             field.deserialize('invalid')
         assert 'Validator <lambda>(invalid) is False' in str(excinfo)
-        assert type(excinfo.value.underlying_exception) == ValidationError
+        assert type(excinfo.value) == ValidationError
 
     def test_field_deserialization_with_user_validator_class_that_returns_bool(self):
         class MyValidator(object):
@@ -553,7 +553,7 @@ class TestFieldDeserialization:
 
         field = fields.Field(validate=MyValidator())
         assert field.deserialize('valid') == 'valid'
-        with pytest.raises(UnmarshallingError) as excinfo:
+        with pytest.raises(ValidationError) as excinfo:
             field.deserialize('invalid')
         assert 'Validator MyValidator(invalid) is False' in str(excinfo)
 
@@ -573,14 +573,14 @@ class TestFieldDeserialization:
         assert field.deserialize('Valid') == 'Valid'
         # validator returns False, so nothing validates
         field2 = fields.String(validate=lambda s: False)
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field2.deserialize('invalid')
 
     def test_field_deserialization_with_validator_with_nonascii_input(self):
         field = fields.String(validate=lambda s: False)
-        with pytest.raises(UnmarshallingError) as excinfo:
+        with pytest.raises(ValidationError) as excinfo:
             field.deserialize(u'привет')
-        assert type(excinfo.value.underlying_exception) == ValidationError
+        assert type(excinfo.value) == ValidationError
 
     def test_field_deserialization_with_user_validators(self):
         validators_gen = (func for func in (lambda s: s.lower() == 'valid',
@@ -596,13 +596,13 @@ class TestFieldDeserialization:
 
         for field in m_colletion_type:
             assert field.deserialize('Valid') == 'Valid'
-            with pytest.raises(UnmarshallingError) as excinfo:
+            with pytest.raises(ValidationError) as excinfo:
                 field.deserialize('invalid')
             assert 'Validator <lambda>(invalid) is False' in str(excinfo)
 
     def test_field_deserialization_with_custom_error_message(self):
         field = fields.String(validate=lambda s: s.lower() == 'valid', error='Bad value.')
-        with pytest.raises(UnmarshallingError) as excinfo:
+        with pytest.raises(ValidationError) as excinfo:
             field.deserialize('invalid')
         assert 'Bad value.' in str(excinfo)
 
@@ -833,7 +833,7 @@ class TestSchemaDeserialization:
             'age': -1,
         }
         v = Validator(strict=True)
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             v.load(bad_data)
 
     def test_strict_mode_many(self):
@@ -842,7 +842,7 @@ class TestSchemaDeserialization:
             {'email': 'bad', 'colors': 'pizza', 'age': -1}
         ]
         v = Validator(strict=True, many=True)
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             v.load(bad_data)
 
     def test_strict_mode_deserialization_with_multiple_validators(self):
@@ -852,7 +852,7 @@ class TestSchemaDeserialization:
             'age': -1,
         }
         v = Validators(strict=True)
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             v.load(bad_data)
 
     def test_uncaught_validation_errors_are_stored(self):
@@ -931,7 +931,7 @@ class TestValidation:
         field = fields.Integer(validate=lambda x: 18 <= x <= 24)
         out = field.deserialize('20')
         assert out == 20
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize(25)
 
     @pytest.mark.parametrize('field', [
@@ -942,7 +942,7 @@ class TestValidation:
     def test_integer_with_validators(self, field):
         out = field.deserialize('20')
         assert out == 20
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize(25)
 
     @pytest.mark.parametrize('field', [
@@ -952,20 +952,20 @@ class TestValidation:
     ])
     def test_float_with_validators(self, field):
         assert field.deserialize(3.14)
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize(4.2)
 
     def test_string_validator(self):
         field = fields.String(validate=lambda n: len(n) == 3)
         assert field.deserialize('Joe') == 'Joe'
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize('joseph')
 
     def test_function_validator(self):
         field = fields.Function(lambda d: d.name.upper(),
                                 validate=lambda n: len(n) == 3)
         assert field.deserialize('joe')
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize('joseph')
 
     @pytest.mark.parametrize('field', [
@@ -978,7 +978,7 @@ class TestValidation:
     ])
     def test_function_validators(self, field):
         assert field.deserialize('joe')
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             field.deserialize('joseph')
 
     def test_method_validator(self):
@@ -989,7 +989,7 @@ class TestValidation:
             def get_name(self, val):
                 return val.upper()
         assert MethodSerializer(strict=True).load({'name': 'joe'})
-        with pytest.raises(UnmarshallingError) as excinfo:
+        with pytest.raises(ValidationError) as excinfo:
             MethodSerializer(strict=True).load({'name': 'joseph'})
         assert 'is False' in str(excinfo)
 
diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py
index 5ee74a23..dbbb1778 100644
--- a/tests/test_exceptions.py
+++ b/tests/test_exceptions.py
@@ -1,7 +1,8 @@
 # -*- coding: utf-8 -*-
+import pytest
 
 from marshmallow.exceptions import ValidationError, MarshallingError, UnmarshallingError
-from marshmallow import fields
+from marshmallow import fields, Schema
 
 
 class TestValidationError:
@@ -19,9 +20,11 @@ class TestValidationError:
         err = ValidationError(messages)
         assert err.messages == messages
 
-    def test_can_store_field_name(self):
-        err = ValidationError('invalid email', field='email')
-        assert err.field == 'email'
+    def test_can_store_field_names(self):
+        err = ValidationError('invalid email', field_names='email')
+        assert err.field_names == ['email']
+        err = ValidationError('invalid email', field_names=['email'])
+        assert err.field_names == ['email']
 
     def test_str(self):
         err = ValidationError('invalid email')
@@ -33,6 +36,9 @@ class TestValidationError:
 
 class TestMarshallingError:
 
+    def test_deprecated(self):
+        pytest.deprecated_call(MarshallingError, 'foo')
+
     def test_can_store_field_and_field_name(self):
         field_name = 'foo'
         field = fields.Str()
@@ -41,8 +47,24 @@ class TestMarshallingError:
         assert err.fields == [field]
         assert err.field_names == [field_name]
 
+    def test_can_be_raised_by_custom_field(self):
+        class MyField(fields.Field):
+            def _serialize(self, val, attr, obj):
+                raise MarshallingError('oops')
+
+        class MySchema(Schema):
+            foo = MyField()
+
+        s = MySchema()
+        result = s.dump({'foo': 42})
+        assert 'foo' in result.errors
+        assert result.errors['foo'] == ['oops']
+
 class TestUnmarshallingError:
 
+    def test_deprecated(self):
+        pytest.deprecated_call(UnmarshallingError, 'foo')
+
     def test_can_store_field_and_field_name(self):
         field_name = 'foo'
         field = fields.Str()
@@ -50,3 +72,15 @@ class TestUnmarshallingError:
                                  field_names=[field_name])
         assert err.fields == [field]
         assert err.field_names == [field_name]
+
+    def test_can_be_raised_by_validator(self):
+        def validator(val):
+            raise UnmarshallingError('oops')
+
+        class MySchema(Schema):
+            foo = fields.Field(validate=[validator])
+
+        s = MySchema()
+        result = s.load({'foo': 42})
+        assert 'foo' in result.errors
+        assert result.errors['foo'] == ['oops']
diff --git a/tests/test_marshalling.py b/tests/test_marshalling.py
index 128e8a82..b75bb814 100644
--- a/tests/test_marshalling.py
+++ b/tests/test_marshalling.py
@@ -4,7 +4,7 @@ import pytest
 
 from marshmallow import fields
 from marshmallow.marshalling import Marshaller, Unmarshaller, null, missing
-from marshmallow.exceptions import UnmarshallingError
+from marshmallow.exceptions import ValidationError
 
 from tests.base import User
 
@@ -88,7 +88,7 @@ class TestUnmarshaller:
             {'email': 'foobar'},
             {'email': 'bar@example.com'}
         ]
-        with pytest.raises(UnmarshallingError) as excinfo:
+        with pytest.raises(ValidationError) as excinfo:
             unmarshal(users, {'email': fields.Email()}, strict=True, many=True)
         assert 'foobar' in str(excinfo)
 
@@ -150,7 +150,7 @@ class TestUnmarshaller:
         assert user['age'] == 71
 
     def test_deserialize_strict_raises_error(self, unmarshal):
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             unmarshal(
                 {'email': 'invalid', 'name': 'Mick'},
                 {'email': fields.Email(), 'name': fields.String()},
diff --git a/tests/test_options.py b/tests/test_options.py
index e0d98b1b..c42673b3 100644
--- a/tests/test_options.py
+++ b/tests/test_options.py
@@ -2,7 +2,7 @@
 import pytest
 
 from marshmallow import fields, Schema
-from marshmallow.exceptions import UnmarshallingError
+from marshmallow.exceptions import ValidationError
 from marshmallow.compat import OrderedDict
 
 from tests.base import *  # noqa
@@ -14,7 +14,7 @@ class TestStrict:
             strict = True
 
     def test_strict_meta_option(self):
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             self.StrictUserSchema().load({'email': 'foo.com'})
 
     def test_strict_meta_option_is_inherited(self):
@@ -24,7 +24,7 @@ class TestStrict:
 
         class ChildStrictSchema(self.StrictUserSchema):
             pass
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError):
             ChildStrictSchema().load({'email': 'foo.com'})
 
 
diff --git a/tests/test_schema.py b/tests/test_schema.py
index bad349a6..1e81ebe1 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -32,7 +32,7 @@ def test_serializer_dump(user):
     # Change strict mode
     s.strict = True
     bad_user = User(name='Monty', age='badage')
-    with pytest.raises(MarshallingError):
+    with pytest.raises(ValidationError):
         s.dump(bad_user)
 
 def test_dump_returns_dict_of_errors():
@@ -42,15 +42,21 @@ def test_dump_returns_dict_of_errors():
     assert 'age' in errors
 
 
-def test_dump_with_strict_mode_raises_error():
-    s = UserSchema(strict=True)
+@pytest.mark.parametrize('SchemaClass',
+[
+    UserSchema, UserMetaSchema
+])
+def test_dump_with_strict_mode_raises_error(SchemaClass):
+    s = SchemaClass(strict=True)
     bad_user = User(name='Monty', email='invalid-email')
-    with pytest.raises(MarshallingError) as excinfo:
+    with pytest.raises(ValidationError) as excinfo:
         s.dump(bad_user)
     exc = excinfo.value
-    assert type(exc.field) == fields.Email
-    assert exc.field_name == 'email'
+    assert type(exc.fields[0]) == fields.Email
+    assert exc.field_names[0] == 'email'
 
+    assert type(exc.messages) == dict
+    assert exc.messages == {'email': ['"invalid-email" is not a valid email address.']}
 
 def test_dump_resets_errors():
     class MySchema(Schema):
@@ -64,6 +70,52 @@ def test_dump_resets_errors():
     assert len(result.errors['email']) == 1
     assert '__invalid' in result.errors['email'][0]
 
+def test_load_resets_errors():
+    class MySchema(Schema):
+        email = fields.Email()
+
+    schema = MySchema()
+    result = schema.load({'name': 'Joe', 'email': 'notvalid'})
+    assert len(result.errors['email']) == 1
+    assert 'notvalid' in result.errors['email'][0]
+    result = schema.load({'name': 'Joe', 'email': '__invalid'})
+    assert len(result.errors['email']) == 1
+    assert '__invalid' in result.errors['email'][0]
+
+def test_dump_resets_error_fields():
+    class MySchema(Schema):
+        email = fields.Email()
+
+    schema = MySchema(strict=True)
+    with pytest.raises(ValidationError) as excinfo:
+        schema.dump(User('Joe', email='notvalid'))
+    exc = excinfo.value
+    assert len(exc.fields) == 1
+    assert len(exc.field_names) == 1
+
+    with pytest.raises(ValidationError) as excinfo:
+        schema.dump(User('Joe', email='__invalid'))
+
+    assert len(exc.fields) == 1
+    assert len(exc.field_names) == 1
+
+def test_load_resets_error_fields():
+    class MySchema(Schema):
+        email = fields.Email()
+
+    schema = MySchema(strict=True)
+    with pytest.raises(ValidationError) as excinfo:
+        schema.load({'name': 'Joe', 'email': 'not-valid'})
+    exc = excinfo.value
+    assert len(exc.fields) == 1
+    assert len(exc.field_names) == 1
+
+    with pytest.raises(ValidationError) as excinfo:
+        schema.load({'name': 'Joe', 'email': '__invalid'})
+
+    assert len(exc.fields) == 1
+    assert len(exc.field_names) == 1
+
 def test_dump_many():
     s = UserSchema()
     u1, u2 = User('Mick'), User('Keith')
@@ -227,8 +279,11 @@ class TestValidate:
 
     def test_validate_strict(self):
         s = UserSchema(strict=True)
-        with pytest.raises(UnmarshallingError):
+        with pytest.raises(ValidationError) as excinfo:
             s.validate({'email': 'bad-email'})
+        exc = excinfo.value
+        assert exc.messages == {'email': ['"bad-email" is not a valid email address.']}
+        assert type(exc.fields[0]) == fields.Email
 
     def test_validate_required(self):
         class MySchema(Schema):
@@ -901,18 +956,19 @@ class TestSchemaValidator:
 
     def test_schema_validation_error_with_stict_stores_correct_field_name(self):
         def validate_with_bool(schema, in_vals):
-            return False
+            raise ValidationError('oops')
 
         class ValidatingSchema(Schema):
             __validators__ = [validate_with_bool]
             field_a = fields.Field()
 
         schema = ValidatingSchema(strict=True)
-        with pytest.raises(UnmarshallingError) as excinfo:
+        with pytest.raises(ValidationError) as excinfo:
             schema.load({'field_a': 1})
         exc = excinfo.value
         assert exc.fields == []
         assert exc.field_names == ['_schema']
+        assert exc.messages == {'_schema': ['oops']}
 
     def test_schema_validation_error_with_strict_when_field_is_specified(self):
         def validate_with_err(schema, inv_vals):
@@ -924,7 +980,7 @@ class TestSchemaValidator:
             field_b = fields.Field()
 
         schema = ValidatingSchema(strict=True)
-        with pytest.raises(UnmarshallingError) as excinfo:
+        with pytest.raises(ValidationError) as excinfo:
             schema.load({'field_a': 1})
         exc = excinfo.value
         assert type(exc.fields[0]) == fields.Str
@@ -946,6 +1002,18 @@ class TestSchemaValidator:
         assert result.errors['field_a'] == ['Something went wrong.']
         assert result.errors['field_b'] == ['Something went wrong.']
 
+        schema = ValidatingSchema(strict=True)
+        with pytest.raises(ValidationError) as excinfo:
+            schema.load({'field_a': 1})
+        err = excinfo.value
+        assert type(err.fields[0]) == fields.Str
+        assert type(err.fields[1]) == fields.Field
+        assert err.field_names == ['field_a', 'field_b']
+        assert err.messages == {
+            'field_a': ['Something went wrong.'],
+            'field_b': ['Something went wrong.']
+        }
+
     def test_validator_with_strict(self):
         def validate_schema(instance, input_vals):
             assert isinstance(instance, Schema)
@@ -958,14 +1026,14 @@ class TestSchemaValidator:
 
         schema = ValidatingSchema(strict=True)
         in_data = {'field_a': 2, 'field_b': 1}
-        with pytest.raises(UnmarshallingError) as excinfo:
+        with pytest.raises(ValidationError) as excinfo:
             schema.load(in_data)
         assert 'Schema validator' in str(excinfo)
         assert 'is False' in str(excinfo)
 
         # underlying exception is a ValidationError
         exc = excinfo.value
-        assert isinstance(exc.underlying_exception, ValidationError)
+        assert isinstance(exc, ValidationError)
 
     def test_validator_defined_by_decorator(self):
         class ValidatingSchema(Schema):
@@ -1458,7 +1526,7 @@ class TestNestedSchema:
         assert "collaborators" not in errors
 
     def test_nested_strict(self):
-        with pytest.raises(UnmarshallingError) as excinfo:
+        with pytest.raises(ValidationError) as excinfo:
             _, errors = BlogSchema(strict=True).load(
                 {'title': "Monty's blog", 'user': {'name': 'Monty', 'email': 'foo'}}
             )
@@ -1679,7 +1747,7 @@ class TestContext:
         owner = User('Joe')
         serializer = UserMethodContextSchema(strict=True)
         serializer.context = None
-        with pytest.raises(MarshallingError) as excinfo:
+        with pytest.raises(ValidationError) as excinfo:
             serializer.dump(owner)
 
         msg = 'No context available for Method field {0!r}'.format('is_owner')
@@ -1694,7 +1762,7 @@ class TestContext:
         serializer = UserFunctionContextSchema(strict=True)
         # no context
         serializer.context = None
-        with pytest.raises(MarshallingError) as excinfo:
+        with pytest.raises(ValidationError) as excinfo:
             serializer.dump(owner)
         msg = 'No context available for Function field {0!r}'.format('is_collab')
         assert msg in str(excinfo)
@@ -1723,23 +1791,6 @@ class TestContext:
         result = ser.dump(obj)
         assert result.data['inner']['likes_bikes'] is True
 
-
-def raise_marshalling_value_error():
-    try:
-        raise ValueError('Foo bar')
-    except ValueError as error:
-        raise MarshallingError(error)
-
-class TestMarshallingError:
-
-    def test_saves_underlying_exception(self):
-        with pytest.raises(MarshallingError) as excinfo:
-            raise_marshalling_value_error()
-        assert 'Foo bar' in str(excinfo)
-        error = excinfo.value
-        assert isinstance(error.underlying_exception, ValueError)
-
-
 def test_error_gets_raised_if_many_is_omitted(user):
     class BadSchema(Schema):
         # forgot to set many=True
diff --git a/tests/test_serialization.py b/tests/test_serialization.py
index 10564d20..41ae0ae4 100644
--- a/tests/test_serialization.py
+++ b/tests/test_serialization.py
@@ -7,7 +7,7 @@ import decimal
 import pytest
 
 from marshmallow import Schema, fields, utils
-from marshmallow.exceptions import MarshallingError
+from marshmallow.exceptions import ValidationError
 from marshmallow.compat import text_type, basestring
 
 from tests.base import User, DummyModel
@@ -92,9 +92,9 @@ class TestFieldSerialization:
         assert field.serialize('m3', user) == decimal.Decimal(1)
         assert isinstance(field.serialize('m4', user), decimal.Decimal)
         assert field.serialize('m4', user) == decimal.Decimal()
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('m5', user)
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('m6', user)
 
         field = fields.Decimal(1)
@@ -106,9 +106,9 @@ class TestFieldSerialization:
         assert field.serialize('m3', user) == decimal.Decimal(1)
         assert isinstance(field.serialize('m4', user), decimal.Decimal)
         assert field.serialize('m4', user) == decimal.Decimal()
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('m5', user)
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('m6', user)
 
         field = fields.Decimal(1, decimal.ROUND_DOWN)
@@ -120,9 +120,9 @@ class TestFieldSerialization:
         assert field.serialize('m3', user) == decimal.Decimal(1)
         assert isinstance(field.serialize('m4', user), decimal.Decimal)
         assert field.serialize('m4', user) == decimal.Decimal()
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('m5', user)
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('m6', user)
 
     def test_decimal_field_string(self, user):
@@ -142,9 +142,9 @@ class TestFieldSerialization:
         assert field.serialize('m3', user) == '1'
         assert isinstance(field.serialize('m4', user), basestring)
         assert field.serialize('m4', user) == '0'
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('m5', user)
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('m6', user)
 
         field = fields.Decimal(1, as_string=True)
@@ -156,9 +156,9 @@ class TestFieldSerialization:
         assert field.serialize('m3', user) == '1.0'
         assert isinstance(field.serialize('m4', user), basestring)
         assert field.serialize('m4', user) == '0'
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('m5', user)
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('m6', user)
 
         field = fields.Decimal(1, decimal.ROUND_DOWN, as_string=True)
@@ -170,9 +170,9 @@ class TestFieldSerialization:
         assert field.serialize('m3', user) == '1.0'
         assert isinstance(field.serialize('m4', user), basestring)
         assert field.serialize('m4', user) == '0'
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('m5', user)
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('m6', user)
 
     def test_function_with_uncallable_param(self):
@@ -182,13 +182,13 @@ class TestFieldSerialization:
     def test_email_field_validates(self, user):
         user.email = 'bademail'
         field = fields.Email()
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('email', user)
 
     def test_url_field_validates(self, user):
         user.homepage = 'badhomepage'
         field = fields.URL()
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('homepage', user)
 
     def test_method_field_with_method_missing(self):
@@ -309,7 +309,7 @@ class TestFieldSerialization:
         field = fields.Select(['male', 'female', 'transexual', 'asexual'])
         assert field.serialize("sex", user) == "male"
         invalid = User('foo', sex='alien')
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('sex', invalid)
 
     def test_datetime_list_field(self):
@@ -321,7 +321,7 @@ class TestFieldSerialization:
     def test_list_field_with_error(self):
         obj = DateTimeList(['invaliddate'])
         field = fields.List(fields.DateTime)
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('dtimes', obj)
 
     def test_datetime_list_serialize_single_value(self):
@@ -366,7 +366,7 @@ class TestFieldSerialization:
 
     def test_arbitrary_field_invalid_value(self, user):
         field = fields.Arbitrary()
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             user.age = 'invalidvalue'
             field.serialize('age', user)
 
@@ -383,7 +383,7 @@ class TestFieldSerialization:
 
     def test_fixed_field_invalid_value(self, user):
         field = fields.Fixed()
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             user.age = 'invalidvalue'
             field.serialize('age', user)
 
@@ -413,7 +413,7 @@ class TestFieldSerialization:
         assert field.serialize('du1', user) == 'bar a'
         assert field.serialize('du2', user) == 'bar b'
         assert field.serialize('du3', user) == 'bar c'
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('du4', user)
 
     def test_query_select_field_string_key(self, user):
@@ -427,7 +427,7 @@ class TestFieldSerialization:
         assert field.serialize('du1', user) == 'a'
         assert field.serialize('du2', user) == 'b'
         assert field.serialize('du3', user) == 'c'
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('du4', user)
 
     def test_query_select_list_field_func_key(self, user):
@@ -442,9 +442,9 @@ class TestFieldSerialization:
         assert field.serialize('du1', user) == ['bar a', 'bar c', 'bar b']
         assert field.serialize('du2', user) == ['bar d', 'bar e', 'bar e']
         assert field.serialize('du5', user) == []
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('du3', user)
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('du4', user)
 
     def test_query_select_list_field_string_key(self, user):
@@ -459,9 +459,9 @@ class TestFieldSerialization:
         assert field.serialize('du1', user) == ['a', 'c', 'b']
         assert field.serialize('du2', user) == ['d', 'e', 'e']
         assert field.serialize('du5', user) == []
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('du3', user)
-        with pytest.raises(MarshallingError):
+        with pytest.raises(ValidationError):
             field.serialize('du4', user)