views:

776

answers:

5

I've created a control derived from ComboBox, and wish to unit test its behaviour.

However, it appears to be behaving differently in my unit test to how it behaves in the real application.

In the real application, the Combobox.DataSource property and the .Items sync up - in other words when I change the Combobox.DataSource the .Items list immediately and automatically updates to show an item for each element of the DataSource.

In my test, I construct a ComboBox, assign a datasource to it, but the .Items list doesn't get updated at all, remaining at 0 items. Thus, when I try to update the .SelectedIndex to 0 in the test to select the first item, I recieve an ArgumentOutOfRangeException.

Is this because I don't have an Application.Run in my unit test starting an event loop, or is this a bit of a red herring?

EDIT: More detail on the first test:

    [SetUp]
    public void SetUp()
    {
        mECB = new EnhancedComboBox();

        mECB.FormattingEnabled = true;
        mECB.Location = new System.Drawing.Point( 45, 4 );
        mECB.Name = "cboFind";
        mECB.Size = new System.Drawing.Size( 121, 21 );
        mECB.TabIndex = 3;

        mECB.AddObserver( this );

        mTestItems = new List<TestItem>();
        mTestItems.Add( new TestItem() { Value = "Billy" } );
        mTestItems.Add( new TestItem() { Value = "Bob" } );
        mTestItems.Add( new TestItem() { Value = "Blues" } );

        mECB.DataSource = mTestItems;
        mECB.Reset();

        mObservedValue = null;
    }

    [Test]
    public void Test01_UpdateObserver()
    {
        mECB.SelectedIndex = 0;
        Assert.AreEqual( "Billy", mObservedValue.Value );
    }

The test fails on the first line, when trying to set the SelectedIndex to 0. On debugging, this appears to be because when the .DataSource is changed, the .Items collection is not updated to reflect this. However, on debugging the real application, the .Items collection is always updated when the .DataSource changes.

Surely I don't have to actually render the ComboBox in the test, I don't even have any drawing surfaces set up to render on to! Maybe the only answer I need is "How do I make the ComboBox update in the same way as when it is drawn, in a unit test scenario where I don't actually need to draw the box?"

A: 

I'm sure that Application.Run absence cannot affects any control's behavior

abatishchev
Then why *does* the ComboBox under test behave differently to that in the real application?
Paul Smith
I think Application.Run only launches this from as main in current thread. Code for Reflector: public static void Run() { ThreadContext.FromCurrent().RunMessageLoop(-1, newApplicationContext()); }
abatishchev
+2  A: 

Since you're simply calling the constructor, a lot of functionality of the combobox will not work. For example, the items will be filled when the ComboBox is drawn on screen, on a form. This does not happen when constructing it in a unit test.

Why do you want to write a unit test on that combobox?

Can't you seperate the logic which now is in the custom control? For example put this in a controller, and test that?

Why don't you test on the DataSource property instead of the Items collection?

Gerrie Schenck
I'm confused by "separate the logic which is now in the custom control? The custom control *is* the combobox. All I want to do is test the functionality I've added by subclassing, but the base combobox is behaving differently in the test. How do I make the combobox behave 'normally'?
Paul Smith
My answer says that a combobox is filled by events caused by being drawn on a form. If you write a test for your combobox as it is now, you are basically testing if it draws correctly. So isn't testing the DataSource property enough?
Gerrie Schenck
OK, some more detail is needed. My EnhancedComboBox maintains a list of observers which it notifies when the selected item changes. So, I make my unit test observe the combobox, and set SelectedIndex to 0 to select the first item in the box. This should result in my test being notified of the
Paul Smith
change in selected item. It doesn't work, because ".SelectedIndex=0" throws an exception. Debugging this, although the .DataSource has 3 items in it, the .Items is empty. In the real application, when the DataSource is changed, .Items catches up automatically so ".SelectedIndex=0" works.
Paul Smith
Ok, I understand it now. Still this will be very hard to do in a unit test, since it involves filling the items collection which is triggered by the combobox being drawn on a real form. Maybe you can look into specialized unit test frameworks for winforms, I don't have any experience with those.
Gerrie Schenck
It might help if you could tell me how you know that the .Items collection is filled on drawing - is there some documentation or blog post or whatever that details this?
Paul Smith
A: 

I'm having the same problem with a combo box where the items are data bound. My current solution is to create a Form in the test, add the combo box to the Controls collection, and then show the form in my test. Kind of ugly. All my combo box really does is list a bunch of TimeSpan objects, sorted, and with custom formatting of the TimeSpan values. It also has special behavior on keypress events. I tried extracting all the data and logic to a separate class but couldn't figure it out. There probably is a better solution but what I'm doing seems satisfactory.

To make testing easier, I created these classes in my test code:

    class TestCombo : DurationComboBox {
        public void SimulateKeyUp(Keys keys) { base.OnKeyUp(new KeyEventArgs(keys)); }
        public DataView DataView { get { return DataSource as DataView; } }
        public IEnumerable<DataRowView> Rows() { return (DataView as IEnumerable).Cast<DataRowView>(); }
        public IEnumerable<int> Minutes() { return Rows().Select(row => (int)row["Minutes"]); }
    }

    class Target {
        public TestCombo Combo { get; private set; }
        public Form Form { get; private set; }

        public Target() {
            Combo = new TestCombo();
            Form = new Form();
            Form.Controls.Add(Combo);
            Form.Show();
        }
    }

Here is a sample test:

           [TestMethod()]
    public void ConstructorCreatesEmptyList() {
        Target t = new Target();
        Assert.AreEqual<int>(0, t.Combo.DataView.Count);
        Assert.AreEqual<int>(-1, t.Combo.SelectedMinutes);
        Assert.IsNull(t.Combo.SelectedItem);
    }
A: 

Even I experienced the same. Got stuck up while writing a unit test for it. @Gerrie Schenck -- Thanks

A: 

This solve some problems if target is ComboBox or any other control:

target.CreateControl();

but I was unable to set SelectedValue it has null value, my test working with two data sources for combo box, one as data source and second is binded to selevted value. With other controls everithing working fine. In the begining I was also creating form in tests, but there is problem when form on created on our build server while tests are executed.