l0b0
l0b0

Reputation: 58858

How to check custom Django model instance equality in tests?

I have a script which imports millions of records from a legacy database into a Django database using raw SQL for efficiency. The legacy database does not have ID primary keys, so these are created from a sequence when inserting rows.

I'm testing that this import process is working properly by creating rows in the legacy database, running the import and verifying that I get the proper Django objects back out. The last part of a test typically looks a bit like this:

actual = MyModel.objects.get(code=legacy_object_code)
expected = MyModel(
    id=actual.id,
    code=legacy_object_code,
    other_property=other_value,
    …
)

I have to pull the ID in from the actual object because otherwise there's no way the two objects could be tested for equality. But the problem is that self.assertEqual(expected, actual) always succeeds! This is because Django object equality is defined as having the same type and ID/PK.

My workaround to actually check the properties of the model instances is this:

def assert_model_instance_field_equality(self, expected, actual):
    expected_dict = expected.__dict__
    expected_dict.pop('_state')
    actual_dict = actual.__dict__
    actual_dict.pop('_state')
    self.assertDictEqual(expected_dict, actual_dict)

I couldn't find this sort of assertion method, but maybe I'm missing something. Is there such a method, am I missing something, or is this really the best way to check field equality?

Upvotes: 1

Views: 1128

Answers (2)

akdom
akdom

Reputation: 33169

I agree with @schillingt's answer in that you should use _meta.get_fields() over __dict__. _meta.get_fields() provides the fields that are meaningful from a Django model's perspective instead of just all fields that happen to be implemented via the Python __dict__ property.

Instead of creating some pseudo-assertion, however, I'd recommend defining an "extractor" that returns the structural bits you care about in your comparison. Pseudo-assertions get a bit fiddly when it comes to readability, and I try to avoid them where I can.

Here's an example of how that might look:

def extract_structural_values(model_instance, ignore_pk='id'):
    field_values = {}
    for field in model_instance._meta.get_fields():
        if field.name == ignore_pk:
            continue

        field_values[field.name] = getattr(model_instance, field.name)
    return field_values

In a test I might use it thusly:

# ...in some test class/function
self.assertEqual(
    extract_structural_values(actual_instance),
    extract_structural_values(expected_instance))
# or even
self.assertEqual(
    extract_structural_values(actual_instance),
    extract_structural_values(SomeModel(
        some_first_field='some value',
        some_second_field='some other value',
    )),
)

Upvotes: 0

schillingt
schillingt

Reputation: 13731

I'd recommend using the _meta.fields property rather than __dict__

def assert_model_instance_fields_equal(self, model, expected, actual):
    for field_name in model._meta.get_fields():
        expected_value = getattr(expected, field_name)
        actual_value = getattr(actual, field_name)
        self.assertEqual(expected_value, actual_value)

Upvotes: 1

Related Questions