Как обрабатывать необязательные поля при сравнении объектов в тестах
Что проверяют
Умеете ли вы сравнивать объекты в тестах, не ломая доменный equals и не превращая тест в двадцать assertEquals по полям. Следом обычно спрашивают, как быть с генерируемыми id, временем создания и порядком элементов в коллекциях.
Как отвечать
Сначала разведите два понятия. equals описывает доменную семантику объекта и не должен меняться ради тестов. Сравнение в тесте это отдельная политика: какие поля важны именно в этом сценарии.
Основной инструмент в Java это рекурсивное сравнение AssertJ:
usingRecursiveComparison(): сравнивает поле за полем, включая вложенные объекты, и выводит понятный отчёт о расхождениях;ignoringFields("id", "createdAt"): исключает поля, значение которых тест не контролирует;ignoringExpectedNullFields(): поля, оставленныеnullв ожидаемом объекте, не сравниваются. Удобно, когда ожидание собирается билдером только из значимых полей;ignoringFieldsOfTypes(...)иignoringFieldsMatchingRegexes(...): для массовых исключений;withComparatorForType(BigDecimal::compareTo, BigDecimal.class): чтобы1.0и1.00считались равными.
Для JSON подходят JSONAssert в режиме LENIENT, где лишние поля в фактическом ответе допустимы, или JsonUnit с опцией IGNORING_EXTRA_FIELDS. В Mockito аргумент с частью полей проверяют через ArgumentCaptor и то же рекурсивное сравнение.
Время лучше не игнорировать, а сделать детерминированным: внедрить Clock и в тесте передать Clock.fixed(...). Тогда поле проверяется, а не выпадает из сравнения.
Где спотыкаются
Переписывают equals так, чтобы он пропускал null. Тест проходит, а в продакшене объекты с разным набором полей начинают считаться равными, и ломаются HashSet и кеши.
Игнорируют слишком много. После ignoringExpectedNullFields() поле, которое забыли заполнить в ожидании, тоже перестаёт проверяться, и тест остаётся зелёным при реальной ошибке.
Сравнивают через toString() или сериализацию в строку. Порядок полей и формат чисел меняются, тест падает без причины, а отчёт о расхождении нечитаем.