Unit Testing WinForms 2.0 Databinding
In a some recent code I was databinding a custom business object to simple controls like TextBoxes and Labels. While I'm not a huge fan of databinding, I am a huge fan of unit testing and TDD.
I would usually prefer to set and get the Text property on my own, but we are using databinding pretty heavily in this project. It made sense to do it this way for consistency's sake.
It is also an area of .net that I am not familiar with. As far as I know there is some magic voodoo going on. I thought using databinding from nUnit would be a good learning experience.
So my initial test looked something like this:
-
[Test ()] _
-
Public Sub ReadsPartNumber
-
' arrange
-
Dim part As New Part()
-
part.PartNumber = "1234"
-
Dim panel As New PartPanel()
-
-
' act
-
txtPartNumber.DataBindings.Add("Text", part, "PartNumber")
-
-
' assert
-
Assert.AreEqual("1234", panel.txtPartNumber.Text)
-
End Sub
and that didn't work. That failed with a expected '1234', but was null message.
Huh, why isn't it set? No idea; so I ask a few coworkers to take a look at it. One suggested maybe databinding doesn't work if the control isn't visible (but I want to test it from nUnit!). Other than that they saw nothing wrong with it. Off to Google!
I found CodeBetter.Com article: Writing unit tests for Windows Forms to test DataBinding. Perfect! Almost the problem I have except he is writing data from the control to the object where I am reading.
It might still work:
-
[Test ()] _
-
Public Sub ReadsPartNumber
-
' arrange
-
Dim part As New Part()
-
part.PartNumber = "1234"
-
Dim panel As New PartPanel()
-
-
' act
-
Dim b As Binding = txtPartNumber.DataBindings.Add("Text", part, "PartNumber")
-
b.ReadValue()
-
-
' assert
-
Assert.AreEqual("1234", panel.txtPartNumber.Text)
-
End Sub
No dice. At this point I got frustrated and found something else to work on.
When I came back I started looking through the Binding class and found an IsBinding property. It returned false in my test. Unfortunately it's read only. I tried a few different things here: panel.Show(), panel.Focus(), anything that might work.
Then I thought "What if I put the panel in a form and show() it?"
-
[Test ()] _
-
Public Sub ReadsPartNumber
-
' arrange
-
Dim part As New Part()
-
part.PartNumber = "1234"
-
Dim panel As New PartPanel()
-
Dim form As New Form()
-
form.Controls.Add(panel)
-
form.Show()
-
-
' act
-
Dim b As Binding = txtPartNumber.DataBindings.Add("Text", part, "PartNumber")
-
-
' assert
-
Assert.AreEqual("1234", panel.txtPartNumber.Text)
-
End Sub
It worked! After the test was working I pulled most of the "arrange" part out into setup or a ShowControl function. I spent most of the day learning databinding and trying to get this to work. Very frustrating.
Moral of the story: listen to your coworkers.
I had the same problem and you don’t have to create a form and show it. Just call the CreateControl() method of your control. In your example the solution would be:
Dim part As New Part()
part.PartNumber = “1234″
Dim panel As New PartPanel()
panel.CreateControl()
txtPartNumber.DataBindings.Add(“Text”, part, “PartNumber”)
Assert.AreEqual(“1234″, panel.txtPartNumber.Text)
That’s it!!!
Sorry, another issue: you need to setup a BindingContext for your control and call CreateControl() method:
part.PartNumber = “1234″
Dim panel As New PartPanel()
panel.CreateControl()
panel.BindingContext = new BindingContext
txtPartNumber.DataBindings.Add(“Text”, part, “PartNumber”)
Assert.AreEqual(“1234″, panel.txtPartNumber.Text)
Now it works!!
Cool. Thanks for the tip. I’ll have to give it a try whenever I get another opportunity to test some databinding.
Wheeee, that CreateControl() call saved my day! :-) The stupid Binding.ReadValue() wouldn’t work properly when the Control wasn’t visible. It seemed to not call the property on the data source, and just use a value 0 (ugh!).
The whole data binding thing doesn’t look super stable, it’s almost worth rewriting the mechanism yourself.
Tnx,
I lost hours resolving this problem.
Solution with form.Show() is known to me before but it wont work on automated build.
panel.CreateControl() solved my problem.
Many tnx.
Fantastic! The new BindingContext() stuff I saw on a different site (http://www.heikniemi.net/hardcoded/2004/12/unit-testing-windows-forms-controls-data-binding/) but the CreateControl really made my tests work. Without it, the Read/WriteValue stuff just wouldn’t work correctly.