chevron-thin-right chevron-thin-left brand cancel-circle search youtube-icon google-plus-icon linkedin-icon facebook-icon twitter-icon toolbox download check linkedin phone twitter-old google-plus facebook profile-male chat calendar profile-male
Welcome to Typemock Community! Here you can ask and receive answers from other community members. If you liked or disliked an answer or thread: react with an up- or downvote.
0 votes
I really like the idea of procedural behavior verification (.WithExactArguments); expected behavior verification (.WasCalledWithArguments) makes things more confusing when you look at the test code. I'd rather declare what I intend to have happen before it does. I end up using .WithExactArguments when I can...

However, when we use custom objects as parameters to a function, it's only .WasCalledWithArguments that offers a .Matching function. So I can't use .WithExactArguments with custom objects as parameters - unless I'm willing to override the .Equals function on the custom object.

The only other way to get around this is to use .DoInstead to test for specific properties on the object... right?

Are there any plans to give .WithExactArguments a custom matcher? Is there already a way to make this work?
asked by jasongb (1.5k points)

7 Answers

0 votes
There actually is another notation which you might find useful.

Say for example you have this:
class Calc {
   public int Add(int x, int y) { return x + y; }

you can then write a test like this:

var c = new Calc();
Isolate.WhenCalled((int x, int y) => c.Add(x, y)).AndArgumentsMatch((x,y) => x>5).WillReturn(9);
var res = c.Add(10, 5);
Assert.AreEqual(res, 9);

Does this answer your question?
answered by yoel (1.9k points)
0 votes
We're almost there! Pardon this oversimplified example, but what if I have the following:

enum Status

class Calc {
  public Status Add ( int x, int y, out int sum ) 
    sum = 0;
    try { sum = x + y; }
    catch { return Failure; }
    return Success;

class UserOfCalc {
  public int AddTwoNumbers ( int x, int y )
    Calc c = new Calc();
    int result = 0;
    Status status = c.Add( x, y, out result );
    if ( status == Success ) return result;
    return -1;

So the software under test is UserOfCalc. I need to set up Calc:

var c = new Calc();
Isolate.WhenCalled( ( int x, int y, out int z ) => c.Add( x, y, out z ) ).AndArgumentsMatch( (x, y, out z ) => x>5 ).WillReturn( Success );

...but I need to be able to dictate the value of the out parameter, too. In other words, I need to be able to specify that the code is CALLED with an OUT parameter whose value is ZERO, AND that the value of that OUT parameter is OTHER THAN ZERO after the call has been made.

And thanks for your patience. I'm almost resigned to the fact that this is either impossible, or inadvisable. It's an aesthetic point, really: since the software under test doesn't care how the dependent object produces its values, all that matters is that the correct values are produced.

I see this as important, however, in that I'm guaranteeing the behavior of my dependencies, and limiting the possibility that my test could pass for other reasons. If I explicitly control the dependent behaviors, then I can be confident that my test is meeting my expectations rather than a fortuitous bug.

Let me know what you think please, and again: thanks.
answered by jasongb (1.5k points)
0 votes

You can do that using a mix of AndArgumentsMatch and WithExactArguments like this:
var c = new Calc();
int n = 0;
Isolate.WhenCalled( ( int x, int y) => c.Add( x, y, out n ) ).
    AndArgumentsMatch( (x, y) => x > 5 ).WithExactArguments().WillReturn(Status.Success);

In the example above WithExactArguments ensures that the behavior will be set only when the out argument is equal to 0.

Please let me know if it helps.
answered by ohad (35.4k points)
0 votes
Thanks for your patience with me!

Using this code as an example, the problem is not that I want the behavior to be set only when my out argument is a certain value. The value of the out parameter when I call the function doesn't matter.

I need to be able to ensure that when the code is called, the out parameter is assigned a specific value.

It boils down to mocking a function that takes an address object and produces two out parameters for latitude and longitude.

When the function is called, the address object is populated, and the lat/long coordinates are both zero.

I need to be able to specify that when an address object with a street address of X will produce lat/long of a/b, and when the address object has a street address of Y it will produce lat/long of c/d.

So I have to call the function with the out parameters being set to zero, but want the mock to return other than zero... Can that be done?
answered by jasongb (1.5k points)
0 votes

It seems like we have an API gap here :(
Once a method with out/ref parameter is faked with WhenCalled the Isolator will always fake the out parameter.

It seems like the Isolator should let you do the following:
Isolate.WhenCalled((int x, int y) => c.Add(x, y, out n)).
    AndArgumentsMatch((x, y) => x > 5).CallOriginal()

This will let you check the arguments while calling the original implementation that will set the out parameter.
Is that what you need?

Note that currently this will not behave correctly because of a bug that cause the Isolator to fake the out parameter.
answered by ohad (35.4k points)
0 votes
Thanks for your reply... It looks like what I want to do isn't possible.

I'd rather not use .CallOriginal; I don't want that code to be exercised at all. It hits a database and has other dependencies as well.

My software under test is dependent on a function that has two out parameters, so the (pseudo)code looks like this:

decimal latitude = 0;
decimal longitude = 0;
GeocodeStatus status = Geocoder.FindLocation( "123 Main Street", out latitude, out longitude );

So I was hoping to set up a mock instance that would allow me to specify that when Geocoder.FindLocation was called with "123 Main Street", it would produce the following:

GeocodeStatus = GeocodeStatus.Success
latitude = 37.32756
longitude = -93.09485

Further, the software under test needs to call Geocoder twice - I'm comparing the distance between two addresses. So I would prefer to specify one result for "123 Main Street", and another for "321 North Second Street".

It sounds like my preferred approach of .WithExactArguments isn't viable, agreed? My code will always call with decimal values of zero for those parameters, so .WithExactArguments will fail when I try to populate the out parameters with the results I want.

This leaves me with my only option being to disregard the input parameters, and basically make sure I cache the results I want in the appropriate order, without using .WithExactArguments. I know it may be irrelevant, but I'd hoped to avoid this, largely for clarity and maintainability. It seems frail to write a test that basically says:

Isolate.WhenCalled( () => fakedGeocoder.FindLocation( "", 37.32756, -93.09485 ) ).WillReturn( GeocodeStatus.Success );
Isolate.WhenCalled( () => fakedGeocoder.FindLocation( "", 34.35321, -90.54234 ) ).WillReturn( GeocodeStatus.Success );

This results in me having to both test the state of the software under test, as well as testing the behavior of the dependencies:

Isolate.Verify.WasCalledWithArguments( () => fakedGeocoder.FindLocation( "123 Main Street", out latitudeA, out longitudeA ).Matching.... etc

It works! And it's so much better than what I would have to craft on my own. I'm grateful for the software, and respect the work you guys do. I guess I just figured that there was a way for me to use your software correctly and accomplish this the way I wanted to. Am I using your software correctly?
answered by jasongb (1.5k points)
0 votes

Actually you can do what you want using DoInstead API.
This will let write your own custom logic base on the arguments.
Isolate.WhenCalled(() => fakedGeocoder.FindLocation("", out x, out y)).
    DoInstead(context =>
                      string address = context.Parameters[0] as string;
                      if (address == "123 Main Street")
                          context.Parameters[1] = 37.32756;
                          context.Parameters[2] = -93.09485;

                      if (address == "321 North Second Street")
                          context.Parameters[1] = 34.35321;
                          context.Parameters[2] = -90.54234;
                      return Status.Success;

Hope I understand you correctly this time :)
answered by ohad (35.4k points)