2

Admittedly, this is a little bit of an odd test case, but it's a problem I've ran in to. I have a class that takes a function as a parameter in it's constructor. I'd like to know if the function that was passed was called. Here's an example:

class TestClassMock extends Mock implements RealClass {
  RealClass _real;

  TestClassMock() {
    _real = new RealClass();

    when(callsTo("myNamedFunction")).alwaysCall(_real.myNamedFunction);
  }
}

class RealClass {
  String _name = "RealClass";
  Function myNamedFunction;

  RealClass() {
    myNamedFunction = _theNamedFunction;
  }

  String _theNamedFunction() {
    return _name;
  }
}

class ClassThatCallsRealClass {
  ClassThatCallsRealClass(Function func) {
    func();
  }
}

//The test
TestClassMock testClassMock = new TestClassMock();
ClassThatCallsRealClass caller = new ClassThatCallsRealClass(testClassMock.myNamedFunction);  
testClassMock.getLogs(callsTo("myNamedFunction")).verify(happenedOnce);

So to explain a bit, ClassThatCallsRealClass takes a function as a parameter and calls it. If you were to pass in (Instance Of RealClass).myNamedFunction, this would in turn call the private function _theNamedFunction on RealClass. However, if you try to mock RealClass and redirect all calls from myNamedFunction to the RealClass myNamedFunction, this seems to fail. I don't see any clear way to get this to work, but I would think it'd be possible.

Any ideas?

mrand01
  • 261
  • 2
  • 9

2 Answers2

5

In Dart, all functions are instances of class Function as you know since you pass an instance of Function to the ClassThatCallsRealClass constructor. Instances of Function have a method call() as shown here.

Meanwhile, Dart has a very good mocking capability described here (with thanks to @KWalrath for the update).

So all you need to do is test with mocks like with any other object. Just as described in the reference, create a spy for ClassThatCallsRealClass and a mock for your Function instance. Then use a verify(happenedOnce) on the call() method of the function.

To mock your function do this:

class MockFunction extends Mock {
  call(int a, int b) => a + b;
}

var mock = new MockFunction();
mock(1,2); //returns 3

Of course the parameter list to call will match that of the real function. Pass mock to your spy on ClassThatCallsRealClass.

Vidya
  • 29,932
  • 7
  • 42
  • 70
1

That worked for me:

library x;

import "package:unittest/unittest.dart";
import "package:unittest/mock.dart";

class TestClassMock extends Mock implements RealClass {
  RealClass _real;

  TestClassMock() {
    _real = new RealClass();

    when(callsTo("myNamedFunction")).alwaysCall(_real.myNamedFunction);
  }
}

class RealClass {
  String _name = "RealClass";
  Function myNamedFunction;

  RealClass() {
    myNamedFunction = _theNamedFunction;
  }

  String _theNamedFunction() {
    return _name;
  }
}

class ClassThatCallsRealClass {
  ClassThatCallsRealClass(Function func) {
    func();
  }
}

class MyFunc implements Function {

  Function func;
  String functionName;

  MyFunc(this.func, this.functionName);

  call() {
    var inv = new MyInvocation(functionName);
    func(inv);
  }
}

main(List<String> args) {
  test('xx', () {
    //The test
    TestClassMock testClassMock = new TestClassMock();
    ClassThatCallsRealClass caller = new ClassThatCallsRealClass(new MyFunc(testClassMock.noSuchMethod, "myNamedFunction"));  
    testClassMock.getLogs(callsTo("myNamedFunction")).verify(happenedOnce);
  });
}

class MyInvocation extends Invocation {
  final String f;
  MyInvocation(this.f); 

  bool get isGetter => false;

  bool get isMethod => true;

  bool get isSetter => false;

  Symbol get memberName => new Symbol(f);

  Map<Symbol, dynamic> get namedArguments => {};

  List get positionalArguments => [];
}

testClassMock.myNamedFunction returns null so I call noSuchMethod directly instead which needs an Invocation. Invocation is abstract so I created an implemented. MyFunc is a class that wraps the function. MyFunc can be called as a function because it implements the call method.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • This modifies the method signature of the constructor in ClassThatCallsRealClass, so unfortunately I can't use it. – mrand01 Dec 09 '13 at 19:44