How do I unit test a lazy async property?

How do I unit test a lazy async property?

Old forum URL: forums.lhotka.net/forums/t/9824.aspx


Kevin Fairclough posted on Thursday, December 02, 2010

I need to test lazy loaded collection to see if its returning the correct items.  The problem I have is this collection uses the following code (which I'm not sure it is correct, as its my first attempt)

 

public static readonly PropertyInfo<ImageList> ImagesProperty = RegisterProperty<ImageList>(c => c.Images);
        public ImageList Images
        {
            get
            {             
                if (!FieldManager.FieldExists(ImagesProperty))
                    if (this.IsNew)
                        LoadProperty(ImagesProperty, ImageList.CreateAsChild());
                    else
                    {
                        ImageList.BeginGetByMajorEntityId(Id, (s, e) =>
                        {
                            if (e.Error == null)
                                LoadProperty(ImagesProperty, e.Object);
                            else
                                throw e.Error;
                        });
                    }

                return GetProperty(ImagesProperty);
            }
            set { SetProperty(ImagesProperty, value); }
        }

It is attempting an async load of the collection. How do I test this using nUnit to see if its working?

Using Csla 4.0  - WPF client.

Regards

Kevin

 

ajj3085 replied on Thursday, December 02, 2010

I think UnitDriven may help:  http://unitdriven.codeplex.com/

RockfordLhotka replied on Thursday, December 02, 2010

Exactly. You need some scheme or framework that allows for the async operation to run, but the test itself to be "suspended" until the result comes back. There are various solutions out there. We created UnitDriven specifically so we could use the same technique in .NET and Silverlight (and now WP7), which is something (to my knowledge) nobody else enables.

The other thing you need is an event that is raised when the async operation is complete. For a lazy load, that's probably the PropertyChanged event from the object, which should be raised when the load is complete so the UI knows to refresh - or in this case so your test knows it is done.

Kevin Fairclough replied on Friday, December 03, 2010

Thanks for the replies.

I have grabbed UnitDriven and created a test.  I'm having a problem where the RunWorkerCompleted is being fired before the factory has loaded the collection.  I'm pretty sure its because of the async Begin inside the property getter.

How can I wait for the thread inside the worker thread (if that makes sense)?

Regards

Kevin

Kevin Fairclough replied on Friday, December 03, 2010

The test:

[Test]
        public void can_get_images_lazily()
        {
            UnitTestContext context = GetContext();
            BackgroundWorker worker = new BackgroundWorker();

            var loc = Location.GetById(savedLocation.Id);
            
            worker.DoWork += (o, e) =>
                                 {
                                     e.Result = loc.Images.Count;
                                 };

            worker.RunWorkerCompleted += (o, e) =>
                                             {
                                                 int expected = 10;
                                                 int actual = (int)e.Result;
                                                 context.Assert.IsNull(e.Error);
                                                 context.Assert.AreEqual(actual, expected);
                                                 context.Assert.Success();
                                             };
            
            worker.RunWorkerAsync();
            context.Complete();

        }

JonnyBee replied on Friday, December 03, 2010

I think you need to call OnPropertyChanged to notify the UI and test code that the value has changed. 
You are also combining Lazy loading and Async loading.

public static readonly PropertyInfo<ImageList> ImagesProperty = RegisterProperty<ImageList>(c => c.Images);
        public ImageList Images
        {
            get
            {             
                if (!FieldManager.FieldExists(ImagesProperty))
                {
                    if (this.IsNew)
                    {
                        LoadProperty(ImagesProperty, ImageList.CreateAsChild());
                    }
                    else
                    {
                        // Start async
                        ImageList.BeginGetByMajorEntityId(Id, (s, e) =>
                        {
                            // callback when async operation is completed
                            if (e.Error == null)
                            {
                                LoadProperty(ImagesProperty, e.Object);
                                // notify UI / test code that the value has been updated and needs to be refreshed
                                OnPropertyChanged(ImagesProperty);
                            }
                            else
                                throw e.Error;
                        });
                        // send null back to caller - will be refreshed when property is loaded.
                        return null;
                    }
               }

                return GetProperty(ImagesProperty);
            }
            set { SetProperty(ImagesProperty, value); }
        }

For each object that is !New the property will return null and the when async operation is complete - load the actual 
object and raise the event OnPropertyChanged to notify UI/TestCode that the value has changed.
HINT: You sould also specify ReleationshipTypes.LazyLoad on the RegisterProperty - then GetProperty will throw an 
error if you try to get value before it has been loaded.

Kevin Fairclough replied on Friday, December 03, 2010

Thanks Johnny

I have put that OnPropertyChanged into my property, however how do I tell the test to wait for the second inner thread?  It just completes before it has had the chance to fill the collection, with an "object reference not set..." error when checking Images property inside DoWork?

Regards

Kevin

Kevin Fairclough replied on Friday, December 03, 2010

Ah, I've got it, I think.

[Test]
        public void can_get_images_lazily()
        {
            UnitTestContext context = GetContext();
            BackgroundWorker worker = new BackgroundWorker();

            var loc = Location.GetById(Location.Id);

            loc.PropertyChanged += (o, e) =>
                                       {                                                                                     
                                           if (e.PropertyName == "Images")
                                           {
                                               context.Assert.AreEqual(loc.Images.Count, 10);
                                               context.Assert.Success();
                                           }
                                       };

            worker.DoWork += (o, e) =>
                                 {
                                     e.Result = loc.Images.Count;                                     
                                 };

            
            worker.RunWorkerAsync();            
            context.Complete();

        }

 

Kevin Fairclough replied on Friday, December 03, 2010

Thanks for the help guys Big Smile

One final problem, now that I'm using OnPropertyChanged how do I inform the test/ui of an error?  The exception is being thrown on a different thread.

Kevin

RockfordLhotka replied on Friday, December 03, 2010

UnitDriven has a Try() method that you can use to wrap the processing to handle such exceptions.

Look at the CSLA unit tests - in particular the SL ones, but some .NET ones too - we have quite a lot of async tests you can use for inspiration.

Copyright (c) Marimer LLC