feat!: nullsafety (#84)

diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index e8b3d93..9c99d4f 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -5,23 +5,21 @@
     branches:
       - master
   pull_request:
-    branches:
-      - master
 
 jobs:
   build:
     runs-on: ubuntu-latest
     container:
-      image: google/dart:2.8.4
+      image: google/dart:2.12-dev
     steps:
       - uses: actions/checkout@v2
       - name: Install Dependencies
         run: pub get
       - name: Format
-        run: dartfmt --dry-run --set-exit-if-changed .
+        run: dart format .
       - name: Analyze
         run: dartanalyzer --fatal-infos --fatal-warnings lib test
       - name: Run tests
-        run: pub run test_coverage
+        run: dart test --coverage=coverage && dart run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info --packages=.packages --report-on=lib
       - name: Check Code Coverage
-        uses: ChicagoFlutter/lcov-cop@v1.0.0
+        uses: VeryGoodOpenSource/very_good_coverage@v1.1.1
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 04dad5b..a437998 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,43 @@
+# 2.0.0
+
+- **BREAKING**: opt into null safety
+  - feat!: upgrade Dart SDK constraints to `>=2.12.0-0 <3.0.0`
+- **BREAKING**: stringify prints "null" for null properties instead of ""
+- feat: `EquatableConfig.stringify` defaults to `true` in debug mode.
+- fix: support legacy equality overrides with `EquatableMixin`
+- fix: iterable equality comparisons ([#101](https://github.com/felangel/equatable/issues/101))
+- fix: stringify instance with long properties ([#94](https://github.com/felangel/equatable/issues/94))
+- chore: update dependencies
+  - `collection: ^1.15.0`
+  - `meta: ^1.3.0`
+- docs: minor updates to `README` and `example`
+
+# 2.0.0-nullsafety.4
+
+- feat: `EquatableConfig.stringify` defaults to `true` in debug mode.
+- fix: support legacy equality overrides with `EquatableMixin`
+
+# 2.0.0-nullsafety.3
+
+- chore: update dependencies
+  - `collection: ^1.15.0`
+  - `meta: ^1.3.0`
+
+# 2.0.0-nullsafety.2
+
+- fix: iterable equality comparisons ([#101](https://github.com/felangel/equatable/issues/101))
+- fix: stringify instance with long properties ([#94](https://github.com/felangel/equatable/issues/94))
+
+# 2.0.0-nullsafety.1
+
+- **BREAKING**: stringify prints "null" for null properties instead of ""
+
+# 2.0.0-nullsafety.0
+
+- **BREAKING**: opt into null safety
+- feat!: upgrade Dart SDK constraints to `>=2.12.0-0 <3.0.0`
+- docs: minor updates to `README` and `example`
+
 # 1.2.6
 
 - fix: iterable equality comparisons ([#101](https://github.com/felangel/equatable/issues/101))
diff --git a/README.md b/README.md
index d11972c..e63982e 100644
--- a/README.md
+++ b/README.md
@@ -42,9 +42,9 @@
 
 ```dart
 class Person {
-  final String name;
-
   const Person(this.name);
+
+  final String name;
 }
 ```
 
@@ -68,10 +68,10 @@
 
 ```dart
 class Person {
-  final String name;
-
   const Person(this.name);
 
+  final String name;
+
   @override
   bool operator ==(Object other) =>
     identical(this, other) ||
@@ -108,7 +108,7 @@
 
 ```yaml
 dependencies:
-  equatable: ^1.2.5
+  equatable: ^2.0.0
 ```
 
 Next, we need to install it:
@@ -127,9 +127,9 @@
 import 'package:equatable/equatable.dart';
 
 class Person extends Equatable {
-  final String name;
+  const Person(this.name);
 
-  Person(this.name);
+  final String name;
 
   @override
   List<Object> get props => [name];
@@ -142,9 +142,9 @@
 import 'package:equatable/equatable.dart';
 
 class Person extends Equatable {
-  final String name;
+  const Person(this.name);
 
-  Person(this.name);
+  final String name;
 
   @override
   List<Object> get props => [name];
@@ -164,10 +164,10 @@
 import 'package:equatable/equatable.dart';
 
 class Person extends Equatable {
-  final String name;
-
   const Person(this.name);
 
+  final String name;
+
   @override
   List<Object> get props => [name];
 }
@@ -175,11 +175,11 @@
 
 ### `toString` Implementation
 
-Equatable can implement `toString` method including all the given props. If you want that behaviour, just include the following:
+Equatable can implement `toString` method including all the given props. If you want that behaviour for a specific `Equatable` object, just include the following:
 
 ```dart
-  @override
-  bool get stringify => true;
+@override
+bool get stringify => true;
 ```
 
 For instance:
@@ -188,10 +188,10 @@
 import 'package:equatable/equatable.dart';
 
 class Person extends Equatable {
-  final String name;
-
   const Person(this.name);
 
+  final String name;
+
   @override
   List<Object> get props => [name];
 
@@ -219,16 +219,18 @@
 If `stringify` is overridden for a specific `Equatable` class, then the value of `EquatableConfig.stringify` is ignored.
 In other words, the local configuration always takes precedence over the global configuration.
 
+_Note: `EquatableConfig.stringify` defaults to `true` in debug mode and `false` in release mode._
+
 ## Recap
 
 ### Without Equatable
 
 ```dart
 class Person {
-  final String name;
-
   const Person(this.name);
 
+  final String name;
+
   @override
   bool operator ==(Object other) =>
     identical(this, other) ||
@@ -247,9 +249,9 @@
 import 'package:equatable/equatable.dart';
 
 class Person extends Equatable {
-  final String name;
+  const Person(this.name);
 
-  Person(this.name);
+  final String name;
 
   @override
   List<Object> get props => [name];
diff --git a/example/main.dart b/example/main.dart
index c2bffdf..c55197f 100644
--- a/example/main.dart
+++ b/example/main.dart
@@ -1,7 +1,7 @@
 import 'package:equatable/equatable.dart';
 
 class Credentials extends Equatable {
-  const Credentials({this.username, this.password});
+  const Credentials({required this.username, required this.password});
 
   final String username;
   final String password;
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index 622eeaf..bcdd28f 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -1,7 +1,7 @@
 name: example
 
 environment:
-  sdk: ">=2.0.0 <3.0.0"
+  sdk: ">=2.12.0-0 <3.0.0"
 
 dependencies:
   equatable:
diff --git a/lib/src/equatable.dart b/lib/src/equatable.dart
index e7b65b8..3f2f4d0 100644
--- a/lib/src/equatable.dart
+++ b/lib/src/equatable.dart
@@ -26,7 +26,7 @@
   /// The list of properties that will be used to determine whether
   /// two instances are equal.
   /// {@endtemplate}
-  List<Object> get props;
+  List<Object?> get props;
 
   /// {@template equatable_stringify}
   /// If set to `true`, the [toString] method will be overridden to output
@@ -40,7 +40,7 @@
   /// `false`.
   /// {@endtemplate}
   // ignore: avoid_returning_null
-  bool get stringify => null;
+  bool? get stringify => null;
 
   @override
   bool operator ==(Object other) =>
diff --git a/lib/src/equatable_config.dart b/lib/src/equatable_config.dart
index 262c2de..dc07587 100644
--- a/lib/src/equatable_config.dart
+++ b/lib/src/equatable_config.dart
@@ -9,12 +9,27 @@
 /// See also:
 /// * [Equatable.stringify]
 class EquatableConfig {
+  /// {@template stringify}
   /// Global [stringify] setting for all [Equatable] instances.
   ///
   /// If [stringify] is overridden for a particular [Equatable] instance,
   /// then the local [stringify] value takes precedence
   /// over [EquatableConfig.stringify].
   ///
-  /// This value defaults to false.
-  static bool stringify = false;
+  /// This value defaults to true in debug mode and false in release mode.
+  /// {@endtemplate}
+  static bool get stringify {
+    if (_stringify == null) {
+      assert(() {
+        _stringify = true;
+        return true;
+      }());
+    }
+    return _stringify ??= false;
+  }
+
+  /// {@macro stringify}
+  static set stringify(bool value) => _stringify = value;
+
+  static bool? _stringify;
 }
diff --git a/lib/src/equatable_mixin.dart b/lib/src/equatable_mixin.dart
index 8253e3b..38d22e2 100644
--- a/lib/src/equatable_mixin.dart
+++ b/lib/src/equatable_mixin.dart
@@ -9,14 +9,14 @@
 /// [operator ==] as well as the [hashCode] based on the provided [props].
 mixin EquatableMixin {
   /// {@macro equatable_props}
-  List<Object> get props;
+  List<Object?> get props;
 
   /// {@macro equatable_stringify}
   // ignore: avoid_returning_null
-  bool get stringify => null;
+  bool? get stringify => null;
 
   @override
-  bool operator ==(Object other) {
+  bool operator ==(Object? other) {
     return identical(this, other) ||
         other is EquatableMixin &&
             runtimeType == other.runtimeType &&
diff --git a/lib/src/equatable_utils.dart b/lib/src/equatable_utils.dart
index c4f2268..e1da7f4 100644
--- a/lib/src/equatable_utils.dart
+++ b/lib/src/equatable_utils.dart
@@ -2,13 +2,13 @@
 import 'package:equatable/equatable.dart';
 
 /// Returns a `hashCode` for [props].
-int mapPropsToHashCode(Iterable props) =>
-    _finish(props?.fold(0, _combine) ?? 0);
+int mapPropsToHashCode(Iterable? props) =>
+    _finish(props == null ? 0 : props.fold(0, _combine));
 
 const DeepCollectionEquality _equality = DeepCollectionEquality();
 
 /// Determines whether [list1] and [list2] are equal.
-bool equals(List list1, List list2) {
+bool equals(List? list1, List? list2) {
   if (identical(list1, list2)) return true;
   if (list1 == null || list2 == null) return false;
   final length = list1.length;
@@ -63,5 +63,5 @@
 }
 
 /// Returns a string for [props].
-String mapPropsToString(Type runtimeType, List<Object> props) =>
-    '$runtimeType(${props?.map((prop) => prop.toString())?.join(', ') ?? ""})';
+String mapPropsToString(Type runtimeType, List<Object?> props) =>
+    '$runtimeType(${props.map((prop) => prop.toString()).join(', ')})';
diff --git a/pubspec.yaml b/pubspec.yaml
index 37fd1db..700428f 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,18 +1,18 @@
 name: equatable
-description: An abstract class that helps to implement equality without needing to explicitly override == and hashCode.
-version: 1.2.6
+description: A Dart package that helps to implement value based equality without needing to explicitly override == and hashCode.
+version: 2.0.0
 repository: https://github.com/felangel/equatable
 issue_tracker: https://github.com/felangel/equatable/issues
 homepage: https://github.com/felangel/equatable
 documentation: https://github.com/felangel/equatable
 
 environment:
-  sdk: ">=2.0.0 <3.0.0"
+  sdk: ">=2.12.0-0 <3.0.0"
 
 dependencies:
-  collection: ^1.14.11
-  meta: ^1.1.6
+  collection: ^1.15.0
+  meta: ^1.3.0
 
 dev_dependencies:
-  test: ^1.14.3
-  test_coverage: ^0.4.1
+  coverage: ^0.14.2
+  test: ^1.16.0
diff --git a/test/custom_list.dart b/test/custom_list.dart
index ecc49c2..4b13834 100644
--- a/test/custom_list.dart
+++ b/test/custom_list.dart
@@ -35,7 +35,7 @@
   List<T> cast<T>() => CustomList<T>(_list.cast<T>(), growable: _growable);
 
   @override
-  bool contains(Object element) => _list.contains(element);
+  bool contains(Object? element) => _list.contains(element);
 
   @override
   E elementAt(int index) => _list.elementAt(index);
@@ -50,7 +50,7 @@
   E get first => _list.first;
 
   @override
-  E firstWhere(bool test(E element), {E orElse()}) =>
+  E firstWhere(bool test(E element), {E orElse()?}) =>
       _list.firstWhere(test, orElse: orElse);
 
   @override
@@ -89,14 +89,14 @@
   E get last => _list.last;
 
   @override
-  int lastIndexOf(E element, [int start]) => _list.lastIndexOf(element, start);
+  int lastIndexOf(E element, [int? start]) => _list.lastIndexOf(element, start);
 
   @override
-  int lastIndexWhere(bool test(E element), [int start]) =>
+  int lastIndexWhere(bool test(E element), [int? start]) =>
       _list.lastIndexWhere(test, start);
 
   @override
-  E lastWhere(bool test(E element), {E orElse()}) =>
+  E lastWhere(bool test(E element), {E orElse()?}) =>
       _list.lastWhere(test, orElse: orElse);
 
   @override
@@ -112,7 +112,7 @@
   E get single => _list.single;
 
   @override
-  E singleWhere(bool test(E element), {E orElse()}) =>
+  E singleWhere(bool test(E element), {E orElse()?}) =>
       _list.singleWhere(test, orElse: orElse);
 
   @override
@@ -122,7 +122,7 @@
   Iterable<E> skipWhile(bool test(E value)) => _list.skipWhile(test);
 
   @override
-  List<E> sublist(int start, [int end]) => _list.sublist(start, end);
+  List<E> sublist(int start, [int? end]) => _list.sublist(start, end);
 
   @override
   Iterable<E> take(int count) => _list.take(count);
@@ -181,13 +181,13 @@
   }
 
   @override
-  void sort([int compare(E a, E b)]) {
+  void sort([int compare(E a, E b)?]) {
     _maybeCopyBeforeWrite();
     _list.sort(compare);
   }
 
   @override
-  void shuffle([Random random]) {
+  void shuffle([Random? random]) {
     _maybeCopyBeforeWrite();
     _list.shuffle(random);
   }
@@ -217,7 +217,7 @@
   }
 
   @override
-  bool remove(Object value) {
+  bool remove(Object? value) {
     _maybeCopyBeforeWrite();
     return _list.remove(value);
   }
@@ -259,7 +259,7 @@
   }
 
   @override
-  void fillRange(int start, int end, [E fillValue]) {
+  void fillRange(int start, int end, [E? fillValue]) {
     _maybeCopyBeforeWrite();
     _list.fillRange(start, end, fillValue);
   }
diff --git a/test/equatable_config_test.dart b/test/equatable_config_test.dart
index 69352c6..08483ba 100644
--- a/test/equatable_config_test.dart
+++ b/test/equatable_config_test.dart
@@ -3,44 +3,58 @@
 import 'package:test/test.dart';
 
 class Credentials extends Equatable {
-  const Credentials({this.username, this.password, this.shouldStringify});
+  const Credentials({
+    required this.username,
+    required this.password,
+    this.shouldStringify,
+  });
 
   final String username;
   final String password;
-  final bool shouldStringify;
+  final bool? shouldStringify;
 
   @override
   List<Object> get props => [username, password];
 
   @override
-  bool get stringify => shouldStringify;
+  bool? get stringify => shouldStringify;
 }
 
 abstract class EquatableBase with EquatableMixin {}
 
 class CredentialsMixin extends EquatableBase {
-  CredentialsMixin({this.username, this.password, this.shouldStringify});
+  CredentialsMixin({
+    required this.username,
+    required this.password,
+    this.shouldStringify,
+  });
 
   final String username;
   final String password;
-  final bool shouldStringify;
+  final bool? shouldStringify;
 
   @override
   List<Object> get props => [username, password];
 
   @override
-  bool get stringify => shouldStringify;
+  bool? get stringify => shouldStringify;
 }
 
 void main() {
   group('EquatableConfig', () {
+    late bool globalStringify;
+
+    setUp(() {
+      globalStringify = EquatableConfig.stringify;
+    });
+
     tearDown(() {
-      EquatableConfig.stringify = false;
+      EquatableConfig.stringify = globalStringify;
     });
 
     group('stringify', () {
-      test('defaults to false', () {
-        expect(EquatableConfig.stringify, isFalse);
+      test('defaults to true', () {
+        expect(EquatableConfig.stringify, isTrue);
       });
 
       test('is used when stringify is not overridden at the instance (false)',
diff --git a/test/equatable_mixin_test.dart b/test/equatable_mixin_test.dart
index 7dfe4c9..8cdb3d2 100644
--- a/test/equatable_mixin_test.dart
+++ b/test/equatable_mixin_test.dart
@@ -14,7 +14,7 @@
   List<Object> get props => const [];
 }
 
-class SimpleEquatable<T> extends EquatableBase {
+class SimpleEquatable<T extends Object> extends EquatableBase {
   SimpleEquatable(this.data);
 
   final T data;
@@ -23,7 +23,7 @@
   List<Object> get props => [data];
 }
 
-class MultipartEquatable<T> extends EquatableBase {
+class MultipartEquatable<T extends Object> extends EquatableBase {
   MultipartEquatable(this.d1, this.d2);
 
   final T d1;
@@ -47,27 +47,27 @@
 class ComplexEquatable extends EquatableBase {
   ComplexEquatable({this.name, this.age, this.hairColor, this.children});
 
-  final String name;
-  final int age;
-  final Color hairColor;
-  final List<String> children;
+  final String? name;
+  final int? age;
+  final Color? hairColor;
+  final List<String>? children;
 
   @override
-  List<Object> get props => [name, age, hairColor, children];
+  List<Object?> get props => [name, age, hairColor, children];
 }
 
 class EquatableData extends EquatableBase {
   EquatableData({this.key, this.value});
 
-  final String key;
+  final String? key;
   final dynamic value;
 
   @override
-  List<Object> get props => [key, value];
+  List<Object?> get props => [key, value];
 }
 
 class Credentials extends EquatableBase {
-  Credentials({this.username, this.password});
+  Credentials({required this.username, required this.password});
 
   factory Credentials.fromJson(Map<String, dynamic> json) {
     return Credentials(
@@ -91,7 +91,7 @@
 }
 
 class ComplexStringify extends ComplexEquatable {
-  ComplexStringify({String name, int age, Color hairColor})
+  ComplexStringify({String? name, int? age, Color? hairColor})
       : super(name: name, age: age, hairColor: hairColor);
 
   @override
@@ -99,28 +99,18 @@
 }
 
 class ExplicitStringifyFalse extends ComplexEquatable {
-  ExplicitStringifyFalse({String name, int age, Color hairColor})
+  ExplicitStringifyFalse({String? name, int? age, Color? hairColor})
       : super(name: name, age: age, hairColor: hairColor);
 
   @override
-  List<Object> get props => [name, age, hairColor];
+  List<Object?> get props => [name, age, hairColor];
 
   @override
   bool get stringify => false;
 }
 
-class NullProps extends Equatable {
-  NullProps();
-
-  @override
-  List<Object> get props => null;
-
-  @override
-  bool get stringify => true;
-}
-
 class IterableWithFlag<T> extends Iterable<T> with EquatableMixin {
-  IterableWithFlag({this.list, this.flag});
+  IterableWithFlag({required this.list, required this.flag});
 
   final bool flag;
   final List<T> list;
@@ -132,15 +122,47 @@
   Iterator<T> get iterator => list.iterator;
 }
 
+class LegacyEqualityOverride {
+  const LegacyEqualityOverride(this.x);
+
+  final int x;
+
+  @override
+  bool operator ==(dynamic o) {
+    if (identical(this, o)) return true;
+
+    return o is LegacyEqualityOverride && o.x == x;
+  }
+
+  @override
+  int get hashCode => x.hashCode;
+}
+
+class LegacyEqualityOverrideEquatable extends LegacyEqualityOverride
+    with EquatableMixin {
+  LegacyEqualityOverrideEquatable(int x, this.y) : super(x);
+
+  final int y;
+
+  @override
+  List<Object?> get props => [x, y];
+}
+
 void main() {
+  late bool globalStringify;
+
   setUp(() {
-    EquatableConfig.stringify = false;
+    globalStringify = EquatableConfig.stringify;
+  });
+
+  tearDown(() {
+    EquatableConfig.stringify = globalStringify;
   });
 
   group('Empty Equatable', () {
     test('should correct toString', () {
       final instance = EmptyEquatable();
-      expect(instance.toString(), 'EmptyEquatable');
+      expect(instance.toString(), 'EmptyEquatable()');
     });
 
     test('should return true when instance is the same', () {
@@ -173,14 +195,13 @@
   group('Simple Equatable (string)', () {
     test('should correct toString', () {
       final instance = SimpleEquatable('simple');
-      expect(instance.toString(), 'SimpleEquatable<String>');
+      expect(instance.toString(), 'SimpleEquatable<String>(simple)');
     });
 
-    test('should correct toString when EquatableConfig.stringify is true', () {
-      EquatableConfig.stringify = true;
+    test('should correct toString when EquatableConfig.stringify is false', () {
+      EquatableConfig.stringify = false;
       final instance = SimpleEquatable('simple');
-      expect(instance.toString(), 'SimpleEquatable<String>(simple)');
-      EquatableConfig.stringify = null;
+      expect(instance.toString(), 'SimpleEquatable<String>');
     });
 
     test('should return true when instance is the same', () {
@@ -225,7 +246,7 @@
   group('Simple Equatable (number)', () {
     test('should correct toString', () {
       final instance = SimpleEquatable(0);
-      expect(instance.toString(), 'SimpleEquatable<int>');
+      expect(instance.toString(), 'SimpleEquatable<int>(0)');
     });
 
     test('should return true when instance is the same', () {
@@ -264,7 +285,7 @@
   group('Simple Equatable (bool)', () {
     test('should correct toString', () {
       final instance = SimpleEquatable(true);
-      expect(instance.toString(), 'SimpleEquatable<bool>');
+      expect(instance.toString(), 'SimpleEquatable<bool>(true)');
     });
 
     test('should return true when instance is the same', () {
@@ -306,7 +327,10 @@
         key: 'foo',
         value: 'bar',
       ));
-      expect(instance.toString(), 'SimpleEquatable<EquatableData>');
+      expect(
+        instance.toString(),
+        'SimpleEquatable<EquatableData>(EquatableData(foo, bar))',
+      );
     });
     test('should return true when instance is the same', () {
       final instance = SimpleEquatable(EquatableData(
@@ -365,7 +389,7 @@
   group('Multipart Equatable', () {
     test('should correct toString', () {
       final instance = MultipartEquatable('s1', 's2');
-      expect(instance.toString(), 'MultipartEquatable<String>');
+      expect(instance.toString(), 'MultipartEquatable<String>(s1, s2)');
     });
 
     test('should return true when instance is the same', () {
@@ -420,7 +444,10 @@
         hairColor: Color.black,
         children: ['Bob'],
       );
-      expect(instance.toString(), 'ComplexEquatable');
+      expect(
+        instance.toString(),
+        'ComplexEquatable(Joe, 40, Color.black, [Bob])',
+      );
     });
     test('should return true when instance is the same', () {
       final instance = ComplexEquatable(
@@ -516,7 +543,7 @@
         }
         ''',
       ) as Map<String, dynamic>);
-      expect(instance.toString(), 'Credentials');
+      expect(instance.toString(), 'Credentials(Admin, admin)');
     });
 
     test('should return true when instance is the same', () {
@@ -633,25 +660,6 @@
     });
   });
 
-  group('Null props Equatable', () {
-    test('should not crash invoking equals method', () {
-      final instanceA = NullProps();
-      final instanceB = NullProps();
-      expect(instanceA == instanceB, true);
-    });
-
-    test('should not crash invoking hascode method', () {
-      final instanceA = NullProps();
-      final instanceB = NullProps();
-      expect(instanceA.hashCode == instanceB.hashCode, true);
-    });
-
-    test('should not crash invoking toString method', () {
-      final instance = NullProps();
-      expect(instance.toString(), 'NullProps()');
-    });
-  });
-
   group('Iterable Equatable', () {
     test('should be equal when different instances have same values', () {
       final instanceA = IterableWithFlag(flag: true, list: [1, 2]);
@@ -693,4 +701,21 @@
       expect(instanceA == instanceB, isFalse);
     });
   });
+
+  group('LegacyEqualityOverride', () {
+    test('should be equal when different instances have same values', () {
+      final instanceA = LegacyEqualityOverrideEquatable(0, 1);
+      final instanceB = LegacyEqualityOverrideEquatable(0, 1);
+
+      expect(instanceA == instanceB, isTrue);
+    });
+
+    test('should not be equal when different instances have different values',
+        () {
+      final instanceA = LegacyEqualityOverrideEquatable(0, 0);
+      final instanceB = LegacyEqualityOverrideEquatable(0, 1);
+
+      expect(instanceA == instanceB, isFalse);
+    });
+  });
 }
diff --git a/test/equatable_test.dart b/test/equatable_test.dart
index 76c7b7d..89dca56 100644
--- a/test/equatable_test.dart
+++ b/test/equatable_test.dart
@@ -18,7 +18,7 @@
   List<Object> get props => [];
 }
 
-class SimpleEquatable<T> extends Equatable {
+class SimpleEquatable<T extends Object> extends Equatable {
   const SimpleEquatable(this.data);
 
   final T data;
@@ -27,7 +27,7 @@
   List<Object> get props => [data];
 }
 
-class MultipartEquatable<T> extends Equatable {
+class MultipartEquatable<T extends Object> extends Equatable {
   MultipartEquatable(this.d1, this.d2);
 
   final T d1;
@@ -51,27 +51,27 @@
 class ComplexEquatable extends Equatable {
   const ComplexEquatable({this.name, this.age, this.hairColor, this.children});
 
-  final String name;
-  final int age;
-  final Color hairColor;
-  final List<String> children;
+  final String? name;
+  final int? age;
+  final Color? hairColor;
+  final List<String>? children;
 
   @override
-  List<Object> get props => [name, age, hairColor, children];
+  List<Object?> get props => [name, age, hairColor, children];
 }
 
 class EquatableData extends Equatable {
-  const EquatableData({this.key, this.value});
+  const EquatableData({required this.key, required this.value});
 
   final String key;
-  final dynamic value;
+  final Object value;
 
   @override
   List<Object> get props => [key, value];
 }
 
 class Credentials extends Equatable {
-  const Credentials({this.username, this.password});
+  const Credentials({required this.username, required this.password});
 
   factory Credentials.fromJson(Map<String, dynamic> json) {
     return Credentials(
@@ -97,12 +97,12 @@
 class ComplexStringify extends Equatable {
   ComplexStringify({this.name, this.age, this.hairColor});
 
-  final String name;
-  final int age;
-  final Color hairColor;
+  final String? name;
+  final int? age;
+  final Color? hairColor;
 
   @override
-  List<Object> get props => [name, age, hairColor];
+  List<Object?> get props => [name, age, hairColor];
 
   @override
   bool get stringify => true;
@@ -128,36 +128,32 @@
 class ExplicitStringifyFalse extends Equatable {
   ExplicitStringifyFalse({this.name, this.age, this.hairColor});
 
-  final String name;
-  final int age;
-  final Color hairColor;
+  final String? name;
+  final int? age;
+  final Color? hairColor;
 
   @override
-  List<Object> get props => [name, age, hairColor];
+  List<Object?> get props => [name, age, hairColor];
 
   @override
   bool get stringify => false;
 }
 
-class NullProps extends Equatable {
-  NullProps();
-
-  @override
-  List<Object> get props => null;
-
-  @override
-  bool get stringify => true;
-}
-
 void main() {
+  late bool globalStringify;
+
   setUp(() {
-    EquatableConfig.stringify = false;
+    globalStringify = EquatableConfig.stringify;
+  });
+
+  tearDown(() {
+    EquatableConfig.stringify = globalStringify;
   });
 
   group('Empty Equatable', () {
     test('should correct toString', () {
       final instance = EmptyEquatable();
-      expect(instance.toString(), 'EmptyEquatable');
+      expect(instance.toString(), 'EmptyEquatable()');
     });
 
     test('should return true when instance is the same', () {
@@ -190,14 +186,13 @@
   group('Simple Equatable (string)', () {
     test('should correct toString', () {
       final instance = SimpleEquatable('simple');
-      expect(instance.toString(), 'SimpleEquatable<String>');
+      expect(instance.toString(), 'SimpleEquatable<String>(simple)');
     });
 
-    test('should correct toString when EquatableConfig.stringify is true', () {
-      EquatableConfig.stringify = true;
+    test('should correct toString when EquatableConfig.stringify is false', () {
+      EquatableConfig.stringify = false;
       final instance = SimpleEquatable('simple');
-      expect(instance.toString(), 'SimpleEquatable<String>(simple)');
-      EquatableConfig.stringify = null;
+      expect(instance.toString(), 'SimpleEquatable<String>');
     });
 
     test('should return true when instance is the same', () {
@@ -215,7 +210,7 @@
 
     test('should return correct toString', () {
       final instance = SimpleEquatable('simple');
-      expect(instance.toString(), 'SimpleEquatable<String>');
+      expect(instance.toString(), 'SimpleEquatable<String>(simple)');
     });
 
     test('should return true when instances are different', () {
@@ -245,9 +240,9 @@
   });
 
   group('Simple Equatable (number)', () {
-    test('should correct toString', () {
+    test('should return correct toString', () {
       final instance = SimpleEquatable(0);
-      expect(instance.toString(), 'SimpleEquatable<int>');
+      expect(instance.toString(), 'SimpleEquatable<int>(0)');
     });
 
     test('should return true when instance is the same', () {
@@ -286,7 +281,7 @@
   group('Simple Equatable (bool)', () {
     test('should correct toString', () {
       final instance = SimpleEquatable(true);
-      expect(instance.toString(), 'SimpleEquatable<bool>');
+      expect(instance.toString(), 'SimpleEquatable<bool>(true)');
     });
 
     test('should return true when instance is the same', () {
@@ -328,7 +323,10 @@
         key: 'foo',
         value: 'bar',
       ));
-      expect(instance.toString(), 'SimpleEquatable<EquatableData>');
+      expect(
+        instance.toString(),
+        'SimpleEquatable<EquatableData>(EquatableData(foo, bar))',
+      );
     });
     test('should return true when instance is the same', () {
       final instance = SimpleEquatable(EquatableData(
@@ -387,7 +385,7 @@
   group('Multipart Equatable', () {
     test('should correct toString', () {
       final instance = MultipartEquatable('s1', 's2');
-      expect(instance.toString(), 'MultipartEquatable<String>');
+      expect(instance.toString(), 'MultipartEquatable<String>(s1, s2)');
     });
 
     test('should return true when instance is the same', () {
@@ -442,7 +440,10 @@
         hairColor: Color.black,
         children: ['Bob'],
       );
-      expect(instance.toString(), 'ComplexEquatable');
+      expect(
+        instance.toString(),
+        'ComplexEquatable(Joe, 40, Color.black, [Bob])',
+      );
     });
     test('should return true when instance is the same', () {
       final instance = ComplexEquatable(
@@ -554,7 +555,7 @@
         }
         ''',
       ) as Map<String, dynamic>);
-      expect(instance.toString(), 'Credentials');
+      expect(instance.toString(), 'Credentials(Admin, admin)');
     });
 
     test('should return true when instance is the same', () {
@@ -853,25 +854,6 @@
       expect(instanceC.toString(), 'ExplicitStringifyFalse');
     });
   });
-
-  group('Null props Equatable', () {
-    test('should not crash invoking equals method', () {
-      final instanceA = NullProps();
-      final instanceB = NullProps();
-      expect(instanceA == instanceB, true);
-    });
-
-    test('should not crash invoking hascode method', () {
-      final instanceA = NullProps();
-      final instanceB = NullProps();
-      expect(instanceA.hashCode == instanceB.hashCode, true);
-    });
-
-    test('should not crash invoking toString method', () {
-      final instance = NullProps();
-      expect(instance.toString(), 'NullProps()');
-    });
-  });
 }
 
 // test that subclasses of `Equatable` can have const constructors