Hey guys,
I am trying to unit test some dispatcher extensions that check for cross thread access, and if it is the case, BeginInvoke on the UI thread. The code is written, tested in Silverlight, but I am having some issues with the unit test runner in WPF. To unit test multithreaded code, I use a ManualResetEvent that I set in the spawned thread. Since I am testing setting properties on a UI element, I then DoEvents (code borrowed from our Disciplette) and finally I assert that the UI element’s property has been set.
Obviously when I test without dispatching, I get an exception, no issues there. With the dispatching, no exception. You could say the test is successful if I don’t have an exception.
However, I would like to test that the property has, indeed, been set from the spawned thread. This check succeeds when I run the unit test alone, or if I debug the test, but it fails if I run all the tests (note: I am using MS Tests, and tried both the built in runner and the Resharper runner, with the same result).
To be honest, I am not sure I can solve that, and it’s OK if this cannot be solved, but just curious if one of you gurus did that kind of things already, and have a trick I missed.
Code (I removed the SL code for clarity)
private const string NewContent = "New content";
private Button _button;
[TestMethod]
public void TestDispatchingToUiThread()
{
_button = new Button
{
Content = "Content1"
};
var manualEvent = new ManualResetEvent(false);
var thread = new Thread(()=>
{
Thread.Sleep(500);
DispatcherExtensions.CheckBeginInvokeOnUi(() => AccessMethodOnUiThread(NewContent));
manualEvent.Set();
});
DispatcherExtensions.Initialize();
thread.Start();
manualEvent.WaitOne(30000);
DoEvents();
Assert.AreEqual(NewContent, _button.Content);
}
private void AccessMethodOnUiThread(string newContent)
{
_button.Content = newContent;
}
public void DoEvents()
{
var frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
new DispatcherOperationCallback(ExitFrames), frame);
Dispatcher.PushFrame(frame);
}
public object ExitFrames(object f)
{
((DispatcherFrame)f).Continue = false;
return null;
}
--
Laurent Bugnion [Microsoft MVP, MCP]
Blog: http://blog.galasoft.ch
Support children in Calcutta: http://www.calcutta-espoir.ch
|
|
as |
Cheers,
Laurent
--
Sent from mobile
>privateconst string NewContent = "New
>content";
>privateButton _button;
>
>[TestMethod]
>publicvoid >TestDispatchingToUiThread()
>{
> _button = new Button
> {
> Content = "Content1"
> };
>
> var >manualEvent = new ManualResetEvent(false);
>
> var >thread = new Thread(()=>
> {
> Thread.Sleep(500);
> DispatcherExtensions.CheckBeginInvokeOnUi(() =>
>AccessMethodOnUiThread(NewContent));
>
>manualEvent.Set();
> });
>
> DispatcherExtensions.Initialize();
>
> thread.Start();
>
> manualEvent.WaitOne(30000);
>
> DoEvents();
> Assert.AreEqual(NewContent,
>_button.Content);
>}
>
>privatevoid >AccessMethodOnUiThread(string newContent)
>{
> _button.Content = newContent;
>}
>
>publicvoid DoEvents()
>{
> var >frame = new DispatcherFrame();
> Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
> new DispatcherOperationCallback(ExitFrames),
>frame);
> Dispatcher.PushFrame(frame);
>}
>
>publicobject ExitFrames(object f)
>{
> ((DispatcherFrame)f).Continue
>= false;
> return null;
>}
>
>
>--
>Laurent Bugnion [Microsoft MVP,
>MCP]
>Blog: http://blog.galasoft.ch
>Web: http://www.galasoft.ch
>Support children in Calcutta: http://www.calcutta-espoir.ch
>
Note that in Silverlight, it works differently: Since the DoEvents method
doesn't work there, what I do is BeginInvoke a "Verify" method which calls
the Assert. This way the method setting the Button's content is enqueued
before the Verify method, and they are executed in that order. Unfortunately
this doesn't work in MSTests, because of the way that tests are executed. In
Silverlight, the test runner is a standard Silverlight application and runs
the tests in a linear manner. On the other hand, when I call BeginInvoke on
the MSTest runner on the main thread, the test just terminates without
executing the queue.
Honestly I wish the MSTest runner was more like the Silverlight test runner,
it is really much easier to program against. I guess I could try in NUnit to
see how it behaves, but I don't really have the motivation right now...
Anyway, thanks for the help and cheers,
Laurent
OK however note the following:
· This is an early version, so any comment is really appreciated
· I tried to find an exact same syntax in Silverlight and WPF, which explains why I need to initialize the DispatcherExtensions prior to usage (or else I have issues in Silverlight).
· Make sure that DispatcherExtensions.Initialize is called on the UI thread. In WPF, this can be done in the static App constructor. In Silverlight, this can be done in Application_Startup right after the RootVisual has been initialized.
Again, comments are welcomed.
public static class DispatcherExtensions
{
public static void Initialize()
{
if (UiDispatcher != null)
{
return;
}
#if SILVERLIGHT
UiDispatcher = Application.Current.RootVisual.Dispatcher;
#else
UiDispatcher = Dispatcher.CurrentDispatcher;
#endif
}
public static Dispatcher UiDispatcher
{
get;
private set;
}
public static void CheckBeginInvokeOnUi(Action action)
{
if (UiDispatcher.CheckAccess())
{
action();
}
else
{
UiDispatcher.BeginInvoke(action);