3

I have a problem mocking an interface with an (async) method. The Interface looks like this:

public interface IDataAccessLayer
    {
        Task<bool> ExistsUserAsync(string username, CancellationToken cancellationToken);

        Task<IUser> CreateUserAsync(string username, string password, DateTime dateOfBirth, CancellationToken cancellationToken);
    }

.. the cancellationToken parameter up there is always created and passed in from the outside during runtime (via NancyFx), and when mocking the calls inside a test method like this:

    var validSignupRequest = new UserSignupRequest()
    {
        Username = "meh-spacey_space",
        DateOfBirth = DateTime.Now.Subtract(TimeSpan.FromDays(365*35)),
        Password = "someproper_passw*rd"
    };

    var testDataAccessLayer = A.Fake<IDataAccessLayer>(options => options.Strict());

    var fakeUserInstance = A.Fake<IUser>();

    A.CallTo(() => fakeUserInstance.Username).Returns(validSignupRequest.Username);
    A.CallTo(() => testDataAccessLayer.ExistsUserAsync(validSignupRequest.Username, CancellationToken.None)).Returns(false); // works
    A.CallTo(() => testDataAccessLayer.CreateUserAsync(validSignupRequest.Username, validSignupRequest.Password, validSignupRequest.DateOfBirth, CancellationToken.None)).Returns(fakeUserInstance); // does not work / throws ExpectationException

.. the mocked call to .ExistsUserAsync(...) does work, the .CreateUserAsync one does not work / throws an ExpectationException.

I've checked the underlying code that is being tested and the call is being made using the same values to the .CreateUserAsync(...) method are used & I suspected the dateOfBirth being the culprit, but at least .Ticks wise it is exactly the same.

I am a bit uncertain as to how FakeItEasy performs the signature / parameter value matching because in theory that call IS mocked, but FakeItEasy says it is not. So far it is winning..

Does anyone know what's wrong in my method mocking call(s) up there?

Jörg Battermann
  • 4,044
  • 5
  • 42
  • 79
  • I explicitely set .Strict() up there to see what / that something is going on here. – Jörg Battermann Oct 12 '14 at 19:25
  • I may be missing something here, but I copied your sample code into a solution, added stub definitions for `IUser` and `UserSignupRequest`, and my test passes. Of course, I only have as much code as you pasted, up to `A.CallTo(() => testDataAccessLayer.CreateUserAsync…)`. (I've put it in https://gist.github.com/blairconrad/a8f90d9d8ea04cee5e84) Are you saying that the `A.CallTo` fails? Or that there's some test code that you have after the third `A.CallTo` that fails? If the latter, please include it. If the former, can you paste the full exception that FakeItEasy raises? – Blair Conrad Oct 13 '14 at 11:11
  • Blair, the very last A.CallTo fails, yes with the aforementioned ExpectationException (the .CreateUserAsync method is referred in the exception's message). I'm away from my machine, I'll post more details later) – Jörg Battermann Oct 13 '14 at 11:27
  • Blair, I tried your code and it works for me as well. Moreover, if I use A.Ignored instead of the validSignupRequest.DateOfBirth parameter ( equivalent to https://gist.github.com/blairconrad/a8f90d9d8ea04cee5e84#file-gistfile1-cs-L46 ).. it works in my very own tests, too. Which is.. odd. – Jörg Battermann Oct 13 '14 at 17:52
  • Blair, sorry for the confusion & I did find the culprit. It was not FakeItEasy per se, but the JSON serialization library underneath (Jil) which looses DateTime precision (it is off by a "few" ticks: Expected:<624450704637900579>. Actual:<624450632637900000>). The Test project utilizes NancyFx's testing framework and hence goes through its (de-)serialization pipeline. So the question is why Jil looses precision, but that's a question to be asked elsewhere. For reference, the gist that shows the problem is here: https://gist.github.com/jbattermann/975b8317b92bdb026fe1 – Jörg Battermann Oct 13 '14 at 19:02

2 Answers2

0

In order to have an "answer" to this question so newcomers don't have to run through the comments and gists and whatnot to figure things out, here's my understanding of what happened:

In the original question, the call

A.CallTo(() => testDataAccessLayer.CreateUserAsync(
                         validSignupRequest.Username,
                         validSignupRequest.Password,
                         validSignupRequest.DateOfBirth,
                         CancellationToken.None))
               .Returns(fakeUserInstance); 

doesn't throw, but later on when the production code was exercised and testDataAccessLayer was called, the configured method was not executed—FakeItEasy didn't think the call matched what was configured.

This is because between setting up the call and the actual call, the DateOfBirth ended up being serialized and deserialized by Jil, as shown in this gist, and the DateTime loses precision, so the parameter is no longer the same. Thus, FakeItEasy sees no need to intercept the call.

As Jörg B. mentions in the comments, there was no problem with FakeItEasy. It was doing the best it could with the data that it was given.

Blair Conrad
  • 233,004
  • 25
  • 132
  • 111
-1

This is just a guess, since I'm unfamiliar with FakeItEasy, but perhaps you need to await the results of the async calls.

A.CallTo(async () => await testDataAccessLayer.CreateUserAsync(   // await the result
                     validSignupRequest.Username, validSignupRequest.Password, validSignupRequest.DateOfBirth, CancellationToken.None))
    .Returns(fakeUserInstance);

Or, depending on how FakeItEasy works internally, it might be in how you check the result:

A.CallTo(() => testDataAccessLayer.CreateUserAsync(
               validSignupRequest.Username, validSignupRequest.Password, validSignupRequest.DateOfBirth, CancellationToken.None))
    .Returns(Task.FromResult(fakeUserInstance)); // test a task, not a real object

A quick search found this page, which leans towards the second solution, but I think either will probably work.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • thanks for the hint, however I forgot to mention up there that I did already try that earlier (Task.FromResult(..) as that's how I initially had all my async/Task-based tests). But that didn't help or change anything in this case. FakeItEasy doesn't even get to the .Returns(..) part as it seems to be unable to 'find' my mocked signature in the first place. – Jörg Battermann Oct 12 '14 at 20:30
  • 1
    It should be clear that an async method returns a Task and therefore your FakeItEasy setup must also return a Task rather than a something. Task.FromResult() is the way to go. Beyond that, you don't show how you are calling the faked methods so it is hard to help. The FakeItEasy wiki specifically covers async methods: https://github.com/FakeItEasy/FakeItEasy/wiki/Faking-async-methods – Tim Long Oct 13 '14 at 00:14