Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for real executions on Mocking classes #734

Open
rafaelsetragni opened this issue Jan 26, 2024 · 1 comment
Open

Support for real executions on Mocking classes #734

rafaelsetragni opened this issue Jan 26, 2024 · 1 comment

Comments

@rafaelsetragni
Copy link

rafaelsetragni commented Jan 26, 2024

Hi everyone,

I am working with Mockito in a Flutter project and facing a challenge in testing classes designed with the Chain of Responsibility pattern. Specifically, I need to partially mock a class where only one real method is executed, and the other related methods are mocked with fake results. After the execution, I need to verify which methods were called or not using verify().called(1) or verifyNever().

Here is a simple example:

import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';

import 'test.mocks.dart';

enum MathOperation {
  add,
  subtract,
  multiply,
  divide,
}

class Calculator {

  int add(int a, int b) => a + b;
  int subtract(int a, int b) => a - b;
  int multiply(int a, int b) => a * b;
  int divide(int a, int b) => a % b;

  int execute(int a, int b, {required MathOperation operation}){
    late int result;
    switch(operation){

      case MathOperation.add:
        result = add(a, b);
        break;

      case MathOperation.subtract:
        result = subtract(a, b);
        break;

      case MathOperation.multiply:
        result = multiply(a, b);
        break;

      case MathOperation.divide:
        result = divide(a, b);
        break;
    }
    return result;
  }
}

@GenerateNiceMocks([MockSpec<Calculator>()])
void main(){
  final MockCalculator mockedCalculator = MockCalculator();
  setUpAll(() {
    when(mockedCalculator
        .execute(any, any, operation: anyNamed('operation'))
    ).thenExecuteRealMethod();

    when(mockedCalculator.add(any, any)).thenReturn(0);
    when(mockedCalculator.subtract(any, any)).thenReturn(0);
    when(mockedCalculator.multiply(any, any)).thenReturn(0);
    when(mockedCalculator.divide(any, any)).thenReturn(0);
  });

  test('calculation methods tests', () {
    final calculator = Calculator();
    expect(calculator.execute(1, 2, operation: MathOperation.add), 3);
    expect(calculator.execute(1, 2, operation: MathOperation.subtract), -1);
    expect(calculator.execute(1, 2, operation: MathOperation.multiply), 2);
    expect(calculator.execute(1, 2, operation: MathOperation.divide), 1);
  });

  test('calculation exception tests', () {
    final calculator = Calculator();
    expect(() => calculator.execute(0, 0, operation: MathOperation.add), returnsNormally);
    expect(() => calculator.execute(0, 0, operation: MathOperation.subtract), returnsNormally);
    expect(() => calculator.execute(0, 0, operation: MathOperation.multiply), returnsNormally);
    expect(() => calculator.execute(0, 0, operation: MathOperation.divide), throwsA(isA<UnsupportedError>()));
  });

  test('addition call tests', () {
    mockedCalculator.execute(0, 0, operation: MathOperation.add);

    verify(mockedCalculator.add(any, any)).called(1);
    verify(mockedCalculator.subtract(any, any)).called(0);
    verify(mockedCalculator.multiply(any, any)).called(0);
    verify(mockedCalculator.divide(any, any)).called(0);
  });

  test('subtraction call tests', () {
    mockedCalculator.execute(0, 0, operation: MathOperation.add);

    verify(mockedCalculator.add(any, any)).called(0);
    verify(mockedCalculator.subtract(any, any)).called(1);
    verify(mockedCalculator.multiply(any, any)).called(0);
    verify(mockedCalculator.divide(any, any)).called(0);
  });

  test('multiplication call tests', () {
    mockedCalculator.execute(0, 0, operation: MathOperation.add);

    verify(mockedCalculator.add(any, any)).called(0);
    verify(mockedCalculator.subtract(any, any)).called(0);
    verify(mockedCalculator.multiply(any, any)).called(1);
    verify(mockedCalculator.divide(any, any)).called(0);
  });

  test('divide call tests', () {
    mockedCalculator.execute(0, 0, operation: MathOperation.add);

    verify(mockedCalculator.add(any, any)).called(0);
    verify(mockedCalculator.subtract(any, any)).called(0);
    verify(mockedCalculator.multiply(any, any)).called(0);
    verify(mockedCalculator.divide(any, any)).called(1);
  });
}

In the provided example, I need to test the Calculator class by mocking its add, subtract, multiply, and divide methods individually while ensuring the execute method uses the real implementation. This is important because each method should be tested separately, without being affected by the others.

Note that in this example I just called thenExecuteRealMethod because this is what Im trying to achieve, but this method doesn't exist and I don't know any replacement for it.

Current Behavior

Currently, Mockito does not seem to support this use case directly. A potential workaround involves extending the Calculator class and overriding methods to throw exceptions, but this approach does not allow the use of Mockito's features like counting executions or other verifications.

Expected Behavior

I am looking for a solution where I can specify which methods to mock and which to keep real in my tests. This would enable precise control over the behavior of the class under test and allow me to verify method calls accurately.

Possible Solutions or Ideas

Perhaps introducing a feature called thenExecuteRealMethod in Mockito that allows partial mocking or selective real method invocations in a mocked class.

@srawlins
Copy link
Member

I believe what you are looking for is a "spy"; execute real implementation code, and gather invocation data (how many calls, what arguments). Mockito does not do spying, and the current implementation does not support this idea, because there is no real instance to call. Here's the feature request for Spy: #575.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants