felangel / mocktail

A mock library for Dart inspired by mockito
https://pub.dev/packages/mocktail
MIT License
617 stars 81 forks source link

Bad state error when the mocked class implements an abstract class #142

Closed premiumbiscuit closed 2 years ago

premiumbiscuit commented 2 years ago

Hey,

I had a question with regards to mocking classes that implement abstract classes. I keep getting this error: Bad state: No method stub was called from within 'when()'. Was a real method called, or perhaps an extension method? whenever I try to mock a class implementing an abstract class. But, if I drop the abstract class then the when clauses capture usages successfully.

Is that by design? And are there workarounds?

abstract class ProgramDataSource {
  Future<String> shady({
    required DateTime day,
    required int daysBefore,
    required int daysAfter,
  });
}

class ProgramDataSourceImpl implements ProgramDataSource { 
  Future<String> shady({
      required day,
      required daysBefore,
      required daysAfter,
    }) =>
        Future.error(Exception());
}

// Testing code

class MockProgramDataSource extends Mock implements ProgramDataSourceImpl {}

when(() => mockProgramDataSource.shady(
            day: any(named: "day"),
            daysBefore: any(named: "daysBefore"),
            daysAfter: any(named: "daysAfter"),
      )).thenAnswer((_) async => Future.value("this worked!"));

// Does not work when "ProgramDataSourceImpl" implements ProgramDataSource
// Otherwise it works
mockProgramDataSource.shady(
        day: 1,
        daysBefore: 2,
        daysAfter: 3,
 )

Thanks so much

renancaraujo commented 2 years ago

Hello there,

The mock should be working fine.

Just tested your example locally with mocktail 0.3.0 and I couldn't reproduce your error.


abstract class ProgramDataSource {
  Future<String> shady({
    required DateTime day,
    required int daysBefore,
    required int daysAfter,
  });
}

class ProgramDataSourceImpl implements ProgramDataSource {
  Future<String> shady({
    required day,
    required daysBefore,
    required daysAfter,
  }) =>
      Future.error(Exception());
}

class MockProgramDataSource extends Mock implements ProgramDataSourceImpl {}

void main(){
  test('something else', () async {
    final mockProgramDataSource = MockProgramDataSource();
    when(() => mockProgramDataSource.shady(
      day: any(named: "day"),
      daysBefore: any(named: "daysBefore"),
      daysAfter: any(named: "daysAfter"),
    )).thenAnswer((_) async => Future.value("this worked!"));

    final result = await mockProgramDataSource.shady(
      day: DateTime.now(),
      daysBefore: 2,
      daysAfter: 3,
    );

    expect(result, 'this worked!');
  });
}

Make sure mockProgramDataSource is an instance of the mock class, not ProgramDataSource directly.

Also, day ins your example is DateTime and you call it with an integer.

premiumbiscuit commented 2 years ago

Yep, my mistake. The actual issue was because I had a function returning an instance of the mock, but with a dynamic type return.


generateMockProgramDataSource() {
  return MockProgramDataSource();
}
final mockProgramDataSource = generateMockProgramDataSource(); // This causes this line to be of type dynamic

// This now won't work.
when(() => mockProgramDataSource.shady(
    day: any(named: "day"),
    daysBefore: any(named: "daysBefore"),
    daysAfter: any(named: "daysAfter"),
  )).thenAnswer((_) async => Future.value("this worked!"));

But adding the correct type signature will make things work as intended.


MockProgramDataSource  generateMockProgramDataSource() {
  return MockProgramDataSource();
}

Apologies for the confusion.