spebbe / dartz

Functional programming in Dart
MIT License
755 stars 59 forks source link

Testing with Either #80

Closed LittleFireflies closed 3 years ago

LittleFireflies commented 3 years ago

Hi, I'm currently working on a clean architecture project using Flutter. I find dartz package is useful to bring either success or failure values.

But, i have issues when it comes to testing.

This is is my test case which won't passed:

test('should load list of cast from repository', () async {
    // arrange
    final tId = 1;
    final tCast = Cast(...);
    final tCastList = <Cast>[tCast];

    when(mockCreditsRepository.loadCastByMovie(tId))
        .thenAnswer((_) async => Right(<Cast>[tCast]));
    // act
    final result = await usecase.execute(tId);
    // assert
    expect(result, Right(<Cast>[tCast]));
});

Those test will return as follows:

Expected: Right<dynamic, List<Cast>>:<Right([Cast(1, department, name, originalName, 0.0, profilePath, character)])>
  Actual: Right<Failure, List<Cast>>:<Right([Cast(1, department, name, originalName, 0.0, profilePath, character)])>

But, somehow it passed when I put both value in the same variable.

final tCastList = <Cast>[tCast];

when(mockCreditsRepository.loadCastByMovie(tId))
        .thenAnswer((_) async => Right(tCastList));
    // act
    final result = await usecase.execute(tId);
    // assert
    expect(result, Right(tCastList));

I think it is because i create 2 difference instance for the test. Is there a way to evaluate only the value of either instead of comparing the instance?

LittleFireflies commented 3 years ago

I use equatable package, so two objects being compared by the value it holds on, instead of the instance. This code below will return true: expect(<Cast>[tCast], <Cast>[tCast]); // return true

Babwenbiber commented 3 years ago

I have the very same problem. I wrote the simplest test you can have for that. Furthermore, I am sure, that in Flutter 1 this would work.


import 'package:dartz/dartz.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  String expectedLeft = "lefti";
  List<int> expectedRight = [1, 2, 3, 4, 5];

  group("test dartz", () {
    test("test left", () {
      final res = getExpected(true);
      expect(res, Left(expectedLeft));
    });

    test("test right", () {
      final res = getExpected(false);
      expect(res, Right(expectedRight));
    });
  });
}

Either<String, List<int>> getExpected(bool isLeft) {
  if (isLeft) {
    return Left("lefti");
  } else {
    return Right([1, 2, 3, 4, 5]);
  }
}

output:

package:test_api                                   expect
package:flutter_test/src/widget_tester.dart 440:3  expect
test/core/data/datasources/dartz_test.dart 16:7    main.<fn>.<fn>

Expected: Right<dynamic, List<int>>:<Right([1, 2, 3, 4, 5])>
  Actual: Right<String, List<int>>:<Right([1, 2, 3, 4, 5])>
spebbe commented 3 years ago

Hi @LittleFireflies and @Babwenbiber!

This looks like a duplicate of https://github.com/spebbe/dartz/issues/39 and https://github.com/spebbe/dartz/issues/45 . Basically, Dart List == doesn't check deep equality by default, but expect special treats "naked" Lists.

expect([1] == [1], true) fails for the same reason.

See the linked tickets for some alternative solutions!

LittleFireflies commented 3 years ago

Ok, well noted @spebbe. Thanks for answering. So it's because of the naked list.

I'm curious, is it possible to add listEquals() as the default comparison for List by default? Because I used to have this problem too when I still use operator and hashcode to compare object manually.

spebbe commented 3 years ago

In the context of tests, it might be possible to write custom matchers that would check for deep list equality in more cases.

For general code, even if changing the semantics of List equality were possible, it would probably break a lot of existing libraries that depend on == being roughly the same as identical for Lists.

LittleFireflies commented 3 years ago

Well then. I'll use result.getOrElse() as a workaround for now. Thanks for the answer. Considered it done and close it.

spebbe commented 3 years ago

Cool! @LittleFireflies, if you haven't already seen it, the | operator is a shortcut for getOrElse(), so depending on your situation you might be able to just do result|null in your expectations.

SuperMuel commented 2 years ago

I wrote a package to simplify testing with dartz. It currently works for Options and Eithers.

If your class overrides ==, you can use the isRightOf (or isLeftOf) that checks whether the Either instance is actually a Right (or Left) instance containing the object you want :

test('gets the root user', (){
    Either<ApiFailure, User> result = getRootUser();
    final expectedUser = User(username='root'); // User class overrides ==
    expect(result, isRightOf(expectedUser));
});

If your object does not overrides == : This is typically the case for Lists. For instance, given two lists var object1 = ['foo']; and var object2 = ['foo']; Checking for equality object1 == object2 returns False. However the matcher equals will compare the content of the two lists and the following test is valid :

test('Test list equality',(){
    final object1 = ['foo'];
    final object2 = ['foo'];
    expect(object1, equals(object2));
}); // test successful

For cases like this, and more generic ones, we have the matcher isRightThat (or isLeftThat) in dartz_test

test('Test list equality in either',(){
    final object1 = ['foo'];
    Either either = Right(object1);
    expect(either, isRightThat(equals(['foo'])));
}); // test successful

The package also includes matchers for the Option type.