This post is about writing unit tests for Winforms when there is threading involved.
[TestMethod]
But it didn't work. Meaning InvokeIfRequired() didn't call the BeginInvoke(). Hence didn't get 100% coverage.
Requirement
The nuget package DotNet.Helpers contain an API ISynchronizeInvokeExtensions which has an extension method InvokeIfRequired(). That provides the feature to invoke code that can manipulate UI controls from a different thread than it was created.
When unit testing the API, the test code has to execute using a properly threaded test.
Expected solution
It could seem very simple as below. Create a control in the current thread and access it from other thread.[TestMethod]
public void WhenCalledFromOtherThread_ShouldUseBeginInvoke() { Control ctrl = new Form(); ctrl.Enabled = false; int threadIdWhereControlCreated = Thread.CurrentThread.ManagedThreadId; int threadIdWhereControlModified = -1; Task task = Task.Run(() => { threadIdWhereControlModified = Thread.CurrentThread.ManagedThreadId; ctrl.InvokeIfRequired(() => { ctrl.Enabled = true; ctrl.Text = "from therad"; }); }); task.Wait(); Assert.IsTrue(threadIdWhereControlCreated != threadIdWhereControlModified && ctrl.Enabled); }
But it didn't work. Meaning InvokeIfRequired() didn't call the BeginInvoke(). Hence didn't get 100% coverage.
Working solution
Below is the working version.
[TestClass] public class ISynchronizeInvokeExtensions_InvokeIfRequired { [TestMethod] public void WhenCalledFromOtherThread_ShouldUseBeginInvoke() { bool finished = false; TestForm testForm = new TestForm(); testForm.Show(); testForm.Finish += (sender, args) => { testForm.InvokeIfRequired(() => { finished = true; testForm.Close(); }); }; testForm.Text = "trigger"; while (!finished) { Application.DoEvents(); Thread.Yield(); } Assert.IsTrue(finished); } } public partial class TestForm : Form { public event EventHandler Finish; public TestForm() { this.TextChanged += (sender, args) => { Thread runner = new Thread(() => { if (Finish != null) Finish(this, EventArgs.Empty); }); runner.Start(); }; } }
As seen in the code, the form is opened using the form.Show(). If that line is not there, the BeginInvoke will not get called.
Note: It works in the local development machine using Visual Studio and AppVeyor during CI/CD process. If the CI/CD tool selected doesn't support showing a form the unit test will not work.
https://github.com/joymon/dotnet-helpers/blob/master/DotNet.Helpers.Tests/WinForms/ISynchronizeInvokeExtensions_InvokeIfRequired.cs
Happy unit testing...
Note: It works in the local development machine using Visual Studio and AppVeyor during CI/CD process. If the CI/CD tool selected doesn't support showing a form the unit test will not work.
Sample
Working code can be found athttps://github.com/joymon/dotnet-helpers/blob/master/DotNet.Helpers.Tests/WinForms/ISynchronizeInvokeExtensions_InvokeIfRequired.cs
Happy unit testing...
No comments:
Post a Comment