Test stream with wrapped array - dart

I'm trying to test that the function emitArray emits a Response.Success an its value is ['test'].
If I emit a List<String> everything works as expected, but once I wrap the result list in a Response<List<String>> the test fails.
The result is emitted, but it fails when comparing with the expected result.
I'm wondering if it's related to the implementation of == in Response.Success, I'm using the default implementation that the IDE provides.
This is not the real code I have, it's just a simple example that is easier to understand to try to identify the issue.
This is my class to test:
class ListResponse {
final _array = BehaviorSubject<Response<List<String>>>();
Stream<Response<List<String>>> get array => _array.stream;
Future<void> emitArray() async {
_array.add(Response.success(['test']));
}
void dispose() {
_array.close();
}
}
This is my test:
void main() {
ListResponse underTest;
setUp(() {
underTest = ListResponse();
});
test('It should emit array', () {
final array = Response.success(['test']);
expect(
underTest.array,
emitsInOrder([
array,
emitsDone,
]),
);
underTest.emitArray();
underTest.dispose();
});
}
This is the error it throws:
Expected: should do the following in order:
• emit an event that SuccessResponse<List<String>>:<SuccessResponse{value: [test]}>
• be done
Actual: <Instance of 'BehaviorSubject<Response<List<String>>>'>
Which: emitted • SuccessResponse{value: [test]}
x Stream closed.
which didn't emit an event that SuccessResponse<List<String>>:<SuccessResponse{value: [test]}>
This is the Response class
class Response<T> {
Response._();
factory Response.success(T value) = SuccessResponse<T>;
factory Response.error(Exception error) = ErrorResponse<T>;
}
class ErrorResponse<T> extends Response<T> {
ErrorResponse(this.error): super._();
final Exception error;
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is ErrorResponse &&
runtimeType == other.runtimeType &&
error == other.error;
#override
int get hashCode => error.hashCode;
#override
String toString() {
return 'ErrorResponse{error: $error}';
}
}
class SuccessResponse<T> extends Response<T> {
SuccessResponse(this.value): super._();
final T value;
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is SuccessResponse &&
runtimeType == other.runtimeType &&
value == other.value;
#override
int get hashCode => value.hashCode;
#override
String toString() {
return 'SuccessResponse{value: $value}';
}
}

I'm wondering if it's related to the implementation of == in Response.Success
Exactly. This particular test is failing because you can't compare Lists with ==:
abstract class List<E> implements EfficientLengthIterable<E> {
...
/**
* Whether this list is equal to [other].
*
* Lists are, by default, only equal to themselves.
* Even if [other] is also a list, the equality comparison
* does not compare the elements of the two lists.
*/
bool operator ==(Object other);
}
As a workaround you can change the implementation to compare objects' string representations instead:
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is SuccessResponse &&
runtimeType == other.runtimeType &&
value.toString() == other.value.toString();
Interestingly, passing unwrapped List<String>s objects passes test. That happens because StreamMatcher uses equals() from matcher package to match events, and equals() can match lists and maps. It first tries to match objects with ==, then checks whether they are Iterable/Set/Map (and deep matches them recursively), and then reports mismatch error.

Related

Dart, overriding not equal operator gives error

I've been developing dart(flutter) for some while and I came upon this Error. I was creating a custom class that had several operator overrides. For explanation purposes, the class looks like this.
class CustomObject {
int big;
int small;
CustomObject(this.big, this.small);
#override
bool operator ==(Object other) {
if (other is CustomObject) {
return big == other.big && small == other.small;
}
return false;
}
#override
int get hashCode => big.hashCode ^ small.hashCode;
#override
bool operator !=(Object other) {
// Error: The string '!=' isn't user-definable operator.
if (other is CustomObject) {
return big != other.big || small != other.small;
}
return false;
}
}
The error occurs on the != operator override. Looking at this website https://www.geeksforgeeks.org/operator-overloading-in-dart/ it says that you can override != operator. I could not find any other source that documents overriding this operator.
My question is, 1. are you supposed to override != in the first place? 2. if so, is are any restrictions on overriding != operators?

Enum equalization with other classes

I've created a class that only takes Enums as parameters. I figured I could create a third Enum where I would manually put every option so they have a better naming.
The only thing is, I can't test if both my third Enum instance and my class instance with the same parameters are equal just by using the == operator. Tried using equatable and considering the Enum instance as my class instance since it does implement it, but nothing works. Of course, I could create a test where all my given parameters are equal, but I just wondered whether I could do something so they return true when using the == operator.
E.g.:
Configuration
enum A {
a,
b;
}
enum B {
c,
d;
}
class Class with EquatableMixin {
const EveryDayOfYear({required this.aValue, required this.bValue});
final A aValue;
final B bValue;
#override
List<Object?> get props => [aValue, bValue];
}
enum C {
ac(Class(aValue: A.a, bValue: B.c)),
ad(Class(aValue: A.a, bValue: B.d)),
bc(Class(aValue: A.b, bValue: B.c)),
bd(Class(aValue: A.b, bValue: B.d));
const C(this._handler);
final Class _handler;
#override
A get aValue => _handler.aValue;
#override
B get bValue => _handler.bValue;
#override
List<Object?> get props => [aValue, bValue];
}
Objective
final instance = Class(aValue: A.a, bValue: B.c);
instance == C.ac; // I would like something so this operation returns true.
As commented by #Levi-Lesches here the answer to my problem was to override my operator ==:
#override
// ignore: hash_and_equals, already implemented by EquatableMixin
bool operator ==(Object other) {
return (super == other) ||
((other is Class) &&
(week == other.aValue) &&
(day == other.bValue));
}
This solved my problem and is fair since my class instance is not an Enum, but my enum constant is actually my class instance.

How to assert objects by its state (all properties values) in a Dart unit test?

I have a class (with more properties than the example, of course).
How to write the following simple test? I know that equality in Dart is by object instance. How to compare the full object state? I tested with the same matcher as well, with no luck.
import 'package:test/test.dart';
class UnderTesting {
var a;
var b;
UnderTesting({this.a, this.b});
}
void main() {
test("compare objects", () {
final obj1 = UnderTesting(a:1, b:2);
final obj2 = UnderTesting(a:1, b:2);
// Next will fail because it is checking if it is the same instance
expect(obj1, equals(obj2));
} );
}
You need to override the == operator (and should therefore also override hashCode) for UnderTesting. The documentation for equals tells us how to does test for equality:
If [expected] is a [Matcher], then it matches using that. Otherwise it tests for equality using == on the expected value.
So you code should be something like this:
import 'package:test/test.dart';
import 'package:quiver/core.dart';
class UnderTesting {
int a;
int b;
UnderTesting({this.a, this.b});
#override
bool operator ==(Object other) =>
(other is UnderTesting) ? (a == other.a && b == other.b) : false;
#override
int get hashCode => hash2(a, b);
}
void main() {
test("compare objects", () {
final obj1 = UnderTesting(a: 1, b: 2);
final obj2 = UnderTesting(a: 1, b: 2);
expect(obj1, equals(obj2)); // All tests passed!
});
}
I can recommend the quiver package for making easy hashCode implementations: https://pub.dev/documentation/quiver/latest/quiver.core/quiver.core-library.html

Change how map keys are checked for equality

I have the following code:
class KeyClass {
int property;
KeyClass(this.property);
}
void main() {
KeyClass kc1 = KeyClass(1);
KeyClass kc2 = KeyClass(2);
Map<KeyClass, String> map = Map();
map[kc1] = 'hello';
map[kc2] = 'world';
...
}
My goal is to for the following two lines to get the same value from my map:
print(map[kc1]); // prints 'hello'
print(map[KeyClass(1)]); // prints 'null', should print 'hello' too!
Is this possible in Dart language?
The default Map implementation is a LinkedHashMap, so it relies on computing hash codes for the keys. There are a few ways you could make your keys compare equal:
Implement KeyClass.operator == and KeyCode.hashCode:
class KeyClass {
int property;
KeyClass(this.property);
bool operator ==(dynamic other) {
return runtimeType == other.runtimeType && property == other.property;
}
int get hashCode => property.hashCode;
}
Use LinkedHashMap directly. LinkedHashMap's constructor allows providing custom callbacks for computing equality and hash codes:
bool keyEquals(KeyClass k1, KeyClass k2) => k1.property == k2.property;
int keyHashCode(KeyClass k) => k.property.hashCode;
Map<KeyClass, String> map = LinkedHashMap<KeyClass, String>(
equals: keyEquals,
hashCode: keyHashCode,
);

Why does Dart sometimes consider Objects as not equal if one is const?

I've got ClassA which holds a a List<ClassB>. ClassB has an string attribute.
If I now have one const Object of ClassA with a list of an object of ClassB completly identical to another non const Object of ClassA with the exact same object of ClassB then these two are not treated as equal.
Why? I could not find any documentation referencing this occurance when looking any documentation regarding equality.
Here's the code:
import 'package:test/test.dart';
void main() {
test('equal', () {
const ClassA a1 = ClassA(list: [ClassB(text: "Mo")]);
ClassA a2 = ClassA(list: [ClassB(text: "Mo"),]);
expect(const [ClassB(text: "Mo")], [ClassB(text: "Mo")]);//true
expect(a1, equals(a2)); //false. Is only true when a2 is const.
});
}
class ClassB {
final String text;
const ClassB({this.text});
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is ClassB &&
runtimeType == other.runtimeType &&
text == other.text;
#override
int get hashCode => text.hashCode;
}
class ClassA {
final List<ClassB> list;
const ClassA({this.list});
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is ClassA &&
runtimeType == other.runtimeType &&
list == other.list;
#override
int get hashCode => list.hashCode;
}
I expected a1 and a2 as being equal.
The problem is that list and other.list are only equal if they are both const (and with the same, const values, of course), as they are then the same object.
package:collections has some useful comparison tools.
Your equals operator can be rewritten as:
import 'package:collection/collection.dart';
...
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is ClassA && ListEquality<ClassB>().equals(list, other.list);
You will also need to change your implementation of hashCode as, with the change above, the classes are now equal but have differing hashCodes. See edit below...
See also.
Edit
class ClassA {
final List<ClassB> list;
final ListEquality<ClassB> equality = const ListEquality<ClassB>();
const ClassA({this.list});
#override
bool operator ==(Object other) {
return identical(this, other) ||
other is ClassA && equality.equals(list, other.list);
}
#override
int get hashCode => equality.hash(list);
}

Resources