diff --git a/swh/model/fields/compound.py b/swh/model/fields/compound.py
index f73117e8e71cc7c9f24078a9162575311c8f53c9..412ebdb1f8bacd0a11c9f14319470d9756f74406 100644
--- a/swh/model/fields/compound.py
+++ b/swh/model/fields/compound.py
@@ -26,11 +26,12 @@ def validate_against_schema(model, schema, value):
 
     if not isinstance(value, dict):
         raise ValidationError(
-            'Unexpected type %(type)s for swh object, expected dict',
+            'Unexpected type %(type)s for %(model)s, expected dict',
             params={
-                'type': value.__class__.__name__
+                'model': model,
+                'type': value.__class__.__name__,
             },
-            code='swh-unexpected-type',
+            code='model-unexpected-type',
         )
 
     errors = defaultdict(list)
@@ -55,7 +56,7 @@ def validate_against_schema(model, schema, value):
                         ValidationError(
                             'Field %(field)s is mandatory',
                             params={'field': key},
-                            code='swh-field-mandatory',
+                            code='model-field-mandatory',
                         )
                     )
 
diff --git a/swh/model/tests/fields/test_compound.py b/swh/model/tests/fields/test_compound.py
new file mode 100644
index 0000000000000000000000000000000000000000..db840a919832082b2af173c56d8e13cc3f3129b2
--- /dev/null
+++ b/swh/model/tests/fields/test_compound.py
@@ -0,0 +1,232 @@
+# Copyright (C) 2015  The Software Heritage developers
+# See the AUTHORS file at the top-level directory of this distribution
+# License: GNU General Public License version 3, or any later version
+# See top-level LICENSE file for more information
+
+import datetime
+import unittest
+
+from nose.tools import istest
+
+from swh.model.exceptions import ValidationError, NON_FIELD_ERRORS
+from swh.model.fields import compound, simple
+
+
+class ValidateCompound(unittest.TestCase):
+    def setUp(self):
+        def validate_always(model):
+            return True
+
+        def validate_never(model):
+            return False
+
+        self.test_model = 'test model'
+        self.test_schema = {
+            'int': (True, simple.validate_int),
+            'str': (True, simple.validate_str),
+            'str2': (True, simple.validate_str),
+            'datetime': (False, simple.validate_datetime),
+            NON_FIELD_ERRORS: validate_always,
+        }
+
+        self.test_schema_shortcut = self.test_schema.copy()
+        self.test_schema_shortcut[NON_FIELD_ERRORS] = validate_never
+
+        self.test_schema_field_failed = self.test_schema.copy()
+        self.test_schema_field_failed['int'] = (True, [simple.validate_int,
+                                                       validate_never])
+
+        self.test_value = {
+            'str': 'value1',
+            'str2': 'value2',
+            'int': 42,
+            'datetime': datetime.datetime(1990, 1, 1, 12, 0, 0,
+                                          tzinfo=datetime.timezone.utc),
+        }
+
+        self.test_value_missing = {
+            'str': 'value1',
+        }
+
+        self.test_value_str_error = {
+            'str': 1984,
+            'str2': 'value2',
+            'int': 42,
+            'datetime': datetime.datetime(1990, 1, 1, 12, 0, 0,
+                                          tzinfo=datetime.timezone.utc),
+        }
+
+        self.test_value_missing_keys = {'int'}
+
+        self.test_value_wrong_type = 42
+
+        self.present_keys = set(self.test_value)
+        self.missing_keys = {'missingkey1', 'missingkey2'}
+
+    @istest
+    def validate_any_key(self):
+        self.assertTrue(
+            compound.validate_any_key(self.test_value, self.present_keys))
+
+        self.assertTrue(
+            compound.validate_any_key(self.test_value,
+                                      self.present_keys | self.missing_keys))
+
+    @istest
+    def validate_any_key_missing(self):
+        with self.assertRaises(ValidationError) as cm:
+            compound.validate_any_key(self.test_value, self.missing_keys)
+
+        exc = cm.exception
+        self.assertEqual(exc.code, 'missing-alternative-field')
+        self.assertEqual(exc.params['missing_fields'],
+                         ', '.join(sorted(self.missing_keys)))
+
+    @istest
+    def validate_all_keys(self):
+        self.assertTrue(
+            compound.validate_all_keys(self.test_value, self.present_keys))
+
+    @istest
+    def validate_all_keys_missing(self):
+        with self.assertRaises(ValidationError) as cm:
+            compound.validate_all_keys(self.test_value, self.missing_keys)
+
+        exc = cm.exception
+        self.assertEqual(exc.code, 'missing-mandatory-field')
+        self.assertEqual(exc.params['missing_fields'],
+                         ', '.join(sorted(self.missing_keys)))
+
+        with self.assertRaises(ValidationError) as cm:
+            compound.validate_all_keys(self.test_value,
+                                       self.present_keys | self.missing_keys)
+
+        exc = cm.exception
+        self.assertEqual(exc.code, 'missing-mandatory-field')
+        self.assertEqual(exc.params['missing_fields'],
+                         ', '.join(sorted(self.missing_keys)))
+
+    @istest
+    def validate_against_schema(self):
+        self.assertTrue(
+            compound.validate_against_schema(self.test_model, self.test_schema,
+                                             self.test_value))
+
+    @istest
+    def validate_against_schema_wrong_type(self):
+        with self.assertRaises(ValidationError) as cm:
+            compound.validate_against_schema(self.test_model, self.test_schema,
+                                             self.test_value_wrong_type)
+
+        exc = cm.exception
+        self.assertEqual(exc.code, 'model-unexpected-type')
+        self.assertEqual(exc.params['model'], self.test_model)
+        self.assertEqual(exc.params['type'],
+                         self.test_value_wrong_type.__class__.__name__)
+
+    @istest
+    def validate_against_schema_mandatory_keys(self):
+        with self.assertRaises(ValidationError) as cm:
+            compound.validate_against_schema(self.test_model, self.test_schema,
+                                             self.test_value_missing)
+
+        # The exception should be of the form:
+        # ValidationError({
+        #     'mandatory_key1': [ValidationError('model-field-mandatory')],
+        #     'mandatory_key2': [ValidationError('model-field-mandatory')],
+        # })
+        exc = cm.exception
+        for key in self.test_value_missing_keys:
+            nested_key = exc.error_dict[key]
+            self.assertIsInstance(nested_key, list)
+            self.assertEqual(len(nested_key), 1)
+            nested = nested_key[0]
+            self.assertIsInstance(nested, ValidationError)
+            self.assertEqual(nested.code, 'model-field-mandatory')
+            self.assertEqual(nested.params['field'], key)
+
+    @istest
+    def validate_against_schema_whole_schema_shortcut_previous_error(self):
+        with self.assertRaises(ValidationError) as cm:
+            compound.validate_against_schema(
+                self.test_model,
+                self.test_schema_shortcut,
+                self.test_value_missing,
+            )
+
+        exc = cm.exception
+        self.assertNotIn(NON_FIELD_ERRORS, exc.error_dict)
+
+    @istest
+    def validate_against_schema_whole_schema(self):
+        with self.assertRaises(ValidationError) as cm:
+            compound.validate_against_schema(
+                self.test_model,
+                self.test_schema_shortcut,
+                self.test_value,
+            )
+
+        # The exception should be of the form:
+        # ValidationError({
+        #     NON_FIELD_ERRORS: [ValidationError('model-validation-failed')],
+        # })
+
+        exc = cm.exception
+        self.assertEquals(set(exc.error_dict.keys()), {NON_FIELD_ERRORS})
+
+        non_field_errors = exc.error_dict[NON_FIELD_ERRORS]
+        self.assertIsInstance(non_field_errors, list)
+        self.assertEquals(len(non_field_errors), 1)
+
+        nested = non_field_errors[0]
+        self.assertIsInstance(nested, ValidationError)
+        self.assertEquals(nested.code, 'model-validation-failed')
+        self.assertEquals(nested.params['model'], self.test_model)
+        self.assertEquals(nested.params['validator'], 'validate_never')
+
+    @istest
+    def validate_against_schema_field_error(self):
+        with self.assertRaises(ValidationError) as cm:
+            compound.validate_against_schema(self.test_model, self.test_schema,
+                                             self.test_value_str_error)
+
+        # The exception should be of the form:
+        # ValidationError({
+        #     'str': [ValidationError('unexpected-type')],
+        # })
+
+        exc = cm.exception
+        self.assertEquals(set(exc.error_dict.keys()), {'str'})
+
+        str_errors = exc.error_dict['str']
+        self.assertIsInstance(str_errors, list)
+        self.assertEquals(len(str_errors), 1)
+
+        nested = str_errors[0]
+        self.assertIsInstance(nested, ValidationError)
+        self.assertEquals(nested.code, 'unexpected-type')
+
+    @istest
+    def validate_against_schema_field_failed(self):
+        with self.assertRaises(ValidationError) as cm:
+            compound.validate_against_schema(self.test_model,
+                                             self.test_schema_field_failed,
+                                             self.test_value)
+
+        # The exception should be of the form:
+        # ValidationError({
+        #     'int': [ValidationError('field-validation-failed')],
+        # })
+
+        exc = cm.exception
+        self.assertEquals(set(exc.error_dict.keys()), {'int'})
+
+        int_errors = exc.error_dict['int']
+        self.assertIsInstance(int_errors, list)
+        self.assertEquals(len(int_errors), 1)
+
+        nested = int_errors[0]
+        self.assertIsInstance(nested, ValidationError)
+        self.assertEquals(nested.code, 'field-validation-failed')
+        self.assertEquals(nested.params['validator'], 'validate_never')
+        self.assertEquals(nested.params['field'], 'int')