68

I'm writing unit tests for a WinRT app, and I am able to invoke non-async private methods using this:

TheObjectClass theObject = new TheObjectClass();
Type objType = typeof(TheObjectClass);
objType.GetTypeInfo()
       .GetDeclaredMethod("ThePrivateMethod")
       .Invoke(theObject, null);

However, if the private method in question is async, the code will continue execution without waiting for it to finish.

How do I add await functionality to this?

jokeefe
  • 1,836
  • 4
  • 22
  • 27
  • Since this is a WinRT app, I have the feeling that reflection/invocation of private members is disallowed. I can't find official documentation of this on google right now, closest is: http://blogs.microsoft.co.il/blogs/sasha/archive/2011/09/17/metro-net-framework-profile-windows-tailored.aspx EDIT: These are unit tests though, so maybe it's a non-issue. :) – Chris Sinclair Feb 05 '13 at 16:04
  • @Chris Sinclair Actually, the code I have above works perfectly fine for private methods. My issue is specifically with asynchronous ones. The issue would apply to public methods invoked via reflection as well. – jokeefe Feb 05 '13 at 20:49

2 Answers2

116

Well you need to use the value returned by the method. Do you know the type? For example, if it's always a Task, you could use:

await (Task) objType.GetTypeInfo()
                    .GetDeclaredMethod("ThePrivateMethod")
                    .Invoke(theObject, null);

If you don't know the return type but know it will be awaitable, you could use dynamic typing:

await (dynamic) objType.GetTypeInfo()
                       .GetDeclaredMethod("ThePrivateMethod")
                       .Invoke(theObject, null);

I would try to avoid having to call a private method by reflection in your unit tests in the first place though. Can you test it indirectly via the public (or internal) API? That's generally preferable.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • But cast to `dynamic` won't work if `GetAwaiter()` is an extension method, which is exactly the case for WinRT `IAsyncAction`. – svick Feb 05 '13 at 16:21
  • @Jon Skeet: Thanks! This did it. It was a `Task` object being returned, so all I needed to do was cast it and add the `await`. I also found that you need to make the unit test method's return type `Task` as well when you make it `async` or it won't be executed by the unit test frame work. – jokeefe Feb 06 '13 at 16:14
  • @Jeff: It would help if you'd give more details, like the compiler error you're getting. It may well be worth creating a new question rather than having a comment discussion about it. – Jon Skeet Nov 01 '13 at 18:00
  • @Jeff: That sounds like you're trying to use `await` without being in an `async` method (or lambda) - that has nothing to do with *what* you're trying to await. – Jon Skeet Nov 01 '13 at 20:21
  • @JonSkeet I think you're right. I don't have the code in front of me, but I think it might have been inside an anonymous method that wasn't marked async. Sorry for the digression, I'll erase my comments. – Jeff Nov 04 '13 at 00:22
  • Is there a way to determine if that method is `async` or not? – Shimmy Weitzhandler Dec 08 '15 at 08:38
  • 1
    @Shimmy: No, and you shouldn't need to. That's just an implementation detail. You should know whether it will perform asynchronously or not, but a method can return a `Task` and perform asynchronously without being implemented via async/await. – Jon Skeet Dec 08 '15 at 08:40
  • Alight I got it now after experimenting with it a bit more. Thanks Jon! Do you ever `Thread.Sleep()`? – Shimmy Weitzhandler Dec 08 '15 at 09:38
  • It worked for me, though i added a slight modification into it for my need `var instance = MyFactory.Instance.Create(myClassName); // custom factory` `var method = instance.GetType().GetMethod(myMethodName);` `await (dynamic) method.Invoke(instance, null);` – Atta H. May 02 '17 at 10:56
  • When I try something like this, I get the compiler error: CS0656: Der vom Compiler angeforderte Member "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create" fehlt. – ygoe Jun 24 '18 at 18:47
  • @ygoe: It sounds like your project may be messed up. It's hard to say more without a lot more information. I suggest you ask a new question. – Jon Skeet Jun 24 '18 at 21:44
  • @JonSkeet Maybe this only works in WinRT and not NETStd. – ygoe Jun 25 '18 at 06:56
  • @ygoe: It works with .NET Standard projects running in regular .NET Core or .NET. Again, rather than trying to get the details in comments, I suggest you ask a new question with all those details. – Jon Skeet Jun 25 '18 at 07:44
  • In Xamarin.Forms for UWP and Android it doesn't works. – Petr Voborník Apr 09 '19 at 08:04
  • @PetrVoborník: I'm afraid with no more information than "it doesn't works" it's unlikely that anyone will be able to help you. I suggest you create a new question with a complete example, specific context (not just "Android" but which runtime you're using) and error message. – Jon Skeet Apr 09 '19 at 08:13
9

Invoke should return an object convertible to Task. Just await that.

If your private method returns void, then you'll need a custom SynchronizationContext, which is messy. It's better to have your methods return Task/Task<T>.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810