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

No way to capture all method calls when parameters are required. #42

Closed
staticcat opened this issue Aug 25, 2014 · 5 comments
Closed

No way to capture all method calls when parameters are required. #42

staticcat opened this issue Aug 25, 2014 · 5 comments

Comments

@staticcat
Copy link
Contributor

When there is a mock required for a class or interface and a setup is required on a particular method. If that method has parameters it becomes very hard to give the correct parameters to link the setup with.

For example if we have the following interface and class to mock.

type
  {$M+}
  IVisitor = interface;

  IElement = interface
    ['{A2F4744E-7ED3-4DE3-B1E4-5D6C256ACBF0}']
    procedure Accept(const AVisitor : IVisitor);
  end;

  IVisitor = interface
    ['{0D150F9C-909A-413E-B29E-4B869C6BC309}']
    procedure Visit(const AElement : IElement);
  end;

  TProjectSaveCheck = class(TInterfacedObject, IVisitor)
  public
    procedure Visit(const AElement : IElement);
  end;
  {$M-}

There is a problem trying to capture all calls to Visit, or even Accept. This means the following mock setup call needs to know the class or interface instance which is passed to the mock call.

var 
    mockVisitor : TMock<IVisitor>;
    element : IElement;
begin
    mockVisitor.Setup.Expect.Once.When.Visit(????);

    element.Accept(mockVisitor);

    mockVisitor.Verify;
end;

The above case has been reduced for brevity sake, the constructor has been removed. Also the element might be the instance to pass in. But if element has more children, and we were to visit all of them this would then make this call fail.

There needs to be away to specify conditions the parameters should meet for the expectation to be met, return result to be returned, etc.

It would be nice to be able to write something along the lines of:

mockVisitor.Setup.Expect.Once.When.Visit(Is.Any<IVisitor>);

I don't believe this is possible at present however as there is no way to reverse where the value came from. Also there is no generic way to override methods to have funcs defined for all parameters.

@staticcat
Copy link
Contributor Author

It would seem libraries like Moq use the following syntax.

mock.Setup(x => x.DoSomething(It.IsAny<string>())).Returns((string s) => s.ToLower());

This is contrary to the fluent API. Also doesn't seem possible with the fluent API design.

Anyone have any thoughts on how this could be implemented?

@jensweigele
Copy link
Contributor

It's not the best approach but you can realize something like this:

mockVisitor.Setup.Expect.Once.When.Visit(Is<IElement>.Any<IVisitor>)

IElement = T
IVisitor = U

The "Is.Any"-Function could return an object that implements "T" (IElement) and another interface like:

IParameterFilter<U> = interface
   ....
   function ParameterMatch(parameter: U): boolean;
 ...
end;

The class could look like:

TParameterFilter<T, U> = class(TVirtualInterface, IParameterFilter)
   ...
   function ParameterMatch(parameter: U): boolean;
   function Instance: T;
   ...
end;

The class should implement "T" via the virtual Interface class (so an instance of this class could be passed to the setup-Call of your method):

On finding a behavior, you could check if the behavior-parameter supports the filter-interface, execute it with the methods-parameter as parameter for "ParameterMatch" and if it returns true, use this behavior.

I hope it's clear what I mean.

@jensweigele
Copy link
Contributor

Another idea for the syntax of the fluent API:
Is.Any.WhichSupports

In this way, the Is.Any-Returns the IParameterFilter-Interface directly an every call of IParameterFilter returns a "U" (IElement).
Unfortunately with this solution it's not possible to call "Is.Any" since Is.Any returns an IParameterFilter - not "T". So you need to write "Is.Any.All" or something like that.

@MartinSedgewick
Copy link
Contributor

Could you not handle it slightly differently, like the following:

mockVisitor.Setup.Expect.Once.WhenAny.Visit

When I need to test a method is called, I write this as

mockVisitor.Setup.ExpectExactly( 'Visit', 1)

@vincentparrett
Copy link
Member

Closing since we've not been able to implement this in any usable way.

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

4 participants