Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Catel - Part 4 of n: Unit testing with Catel

, 28 Jan 2011 CPOL
This article explains how to write unit tests for MVVM using Catel.
Catel-04_01-unittesting.zip
src
Catel.Articles.04 - Unit testing
Models
Properties
Settings.settings
Resources
Images
add.png
delete.png
edit.png
group.png
UI
Data
Converters
ViewModels
Windows
Catel.Articles.04 - Unit testing.Test
Models
Properties
UI
ViewModels
Catel.Articles.Base
Data
Attributes
Properties
Settings.settings
Resources
Images
CatenaLogic.png
Preview.png
Run.png
ShowCode.png
UI
Controls
Helpers
ViewModels
Windows
Catel.Core
Attributes
ClassDiagrams
DataObjectBase.cd
SavableDataObjectBase.cd
Collections
Helpers
ComponentModel
Data
Attributes
Exceptions
Interfaces
Diagnostics
Extensions
Helpers
Exceptions
Helpers
IO
Exceptions
IoC
LLBLGen
Log4net
Appender
Extensions
Helpers
MVVM
Commands
Interfaces
Exceptions
Services
EventArgs
Exceptions
Interfaces
ViewModels
Attributes
Interfaces
Properties
Reflection
Exceptions
Extensions
Helpers
Runtime
Serialization
Attributes
Helpers
Security
Cryptography
Helpers
Catel.Examples.Models
Properties
Catel.Examples.PersonApplication
Properties
Settings.settings
Resources
Images
add.png
delete.png
edit.png
group.png
UI
Data
Converters
ViewModels
Windows
Catel.Examples.Silverlight
Properties
Resources
Images
add.png
delete.png
edit.png
group.png
UI
Data
Converters
Pages
ViewModels
Windows
Catel.Examples.Silverlight.Web
Catel.Examples.Silverlight.Web.csproj.user
ClientBin
Properties
Catel.FxCop
Catel.Silverlight
Diagnostics
log4net
Core
MVVM
Commands
Services
ViewModels
Properties
Catel.Core
Catel.Windows
Reflection
Themes
Generic
Assets
Old
Windows
Controls
Data
Converters
Helpers
Helpers
Catel.Silverlight.Test
Properties
Catel.Silverlight.Test.Web
Catel.Silverlight.Test.Web.csproj.user
ClientBin
Properties
Catel.snk
Catel.Templates.WpfApplication
Properties
Settings.settings
UI
Controls
ViewModels
Windows
Catel.Templates.WpfItemTemplates
Properties
UI
Controls
ViewModels
Windows
Catel.Test
Collections
Convert
Data
Helpers
IO
MVVM
UI
ViewModels
Properties
Reflection
Runtime
Serialization
Security
Cryptography
Test References
Catel.Windows.accessor
Windows
Data
Converters
Catel.vsmdi
Catel.Windows
ClassDiagrams
ViewModelBase.cd
Collections
Extensions
Helpers
MVVM
Commands
Services
Test
UI
ViewModels
Properties
Settings.settings
Resources
Images
Add.png
ClearOutput.png
Edit.png
Error.png
Loading.gif
Preview.png
Remove.png
Save.png
TipOfTheDay.png
Warning.png
Themes
Aero
ExpressionDark
Assets
Generic
Assets
Controls
Jetpack
Assets
background.png
Old
SunnyOrange
Assets
Windows
Controls
Extensions
LinkLabel
StackGrid
Data
Converters
Helpers
Documents
Extensions
Extensions
Helpers
Input
Markup
Media
Effects
EmptyEffect
EmptyEffect.fx
EmptyEffect.ps
EmptyEffect.fx
GrayscaleEffect
GrayscaleEffect.fx
GrayscaleEffect.ps
Extensions
Imaging
Extensions
Windows
DataWindow
TipOfTheDay
Local.testsettings
Settings.StyleCop
TraceAndTestImpact.testsettings
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SavableDataObjectBase.cs" company="Catel development team">
//   Copyright (c) 2008 - 2011 Catel development team. All rights reserved.
// </copyright>
// <summary>
//   Abstract class that makes the <see cref="DataObjectBase{T}" /> serializable.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using Catel.Properties;
using Catel.Runtime.Serialization;
using log4net;

#if !SILVERLIGHT
using System.Runtime.Serialization.Formatters.Binary;
#endif

namespace Catel.Data
{
    /// <summary>
    /// Abstract class that makes the <see cref="DataObjectBase{T}"/> serializable.
    /// </summary>
    /// <typeparam name="T">Type that the class should hold (same as the defined type).</typeparam>
#if SILVERLIGHT
    [DataContract]
#else
    [Serializable]
#endif
    public abstract class SavableDataObjectBase<T> : DataObjectBase<T>, ISavableDataObjectBase where T : class
    {
        #region Constructor & destructor
        /// <summary>
        /// Initializes a new instance of the <see cref="SavableDataObjectBase{T}"/> class.
        /// </summary>
        protected SavableDataObjectBase()
        {
            if (ContainsNonSerializableMembers)
            {
            	throw new NotSupportedException(Exceptions.NonSerializablePropertiesNotSupported);
            }
        }

#if !SILVERLIGHT
        /// <summary>
        /// Initializes a new instance of the <see cref="SavableDataObjectBase&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="info">SerializationInfo object, null if this is the first time construction.</param>
        /// <param name="context">StreamingContext object, simple pass a default new StreamingContext() if this is the first time construction.</param>
        /// <remarks>
        /// Call this method, even when constructing the object for the first time (thus not deserializing).
        /// </remarks>
        protected SavableDataObjectBase(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
#endif
        #endregion

        #region Properties
        /// <summary>
        /// Gets the bytes of the current binary serialized data object.
        /// </summary>
        /// <value>The bytes.</value>
        [Browsable(false)]
        [XmlIgnore]
        public byte[] Bytes { get { return ToByteArray(); } }
        #endregion

        #region Methods
        /// <summary>
        /// Serializes the object to and xml object.
        /// </summary>
        /// <returns><see cref="XDocument"/> containing the serialized data.</returns>
        public XDocument ToXml()
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
                Save(memoryStream, SerializationMode.Xml);

                memoryStream.Position = 0L;

                using (XmlReader xmlReader = XmlReader.Create(memoryStream))
                {
                    return (XDocument)XNode.ReadFrom(xmlReader);
                }
            }
        }

        /// <summary>
        /// Serializes the object to a byte array.
        /// </summary>
        /// <returns>Byte array containing the serialized data.</returns>
        public byte[] ToByteArray()
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
#if SILVERLIGHT
                Save(memoryStream, SerializationMode.Xml);
#else
                Save(memoryStream, SerializationMode.Binary);
#endif

                return memoryStream.ToArray();
            }
        }

        #region Saving
        /// <summary>
        /// Saves the object to a file using the default formatting.
        /// </summary>
        /// <param name="fileName">Filename of the file that will contain the serialized data of this object.</param>
        public void Save(string fileName)
        {
            Save(fileName, Mode);
        }

        /// <summary>
        /// Saves the object to a file using a specific formatting.
        /// </summary>
        /// <param name="fileName">Filename of the file that will contain the serialized data of this object.</param>
        /// <param name="mode"><see cref="SerializationMode"/> to use.</param>
        public void Save(string fileName, SerializationMode mode)
        {
            FileInfo fileInfo = new FileInfo(fileName);
            if (!Directory.Exists(fileInfo.DirectoryName))
            {
                Directory.CreateDirectory(fileInfo.DirectoryName);
            }

            using (Stream stream = new FileStream(fileName, FileMode.Create))
            {
                Save(stream, mode);
            }
        }

        /// <summary>
        /// Saves the object to a stream using the default formatting.
        /// </summary>
        /// <param name="stream">Stream that will contain the serialized data of this object.</param>
        public void Save(Stream stream)
        {
            Save(stream, Mode);
        }

        /// <summary>
        /// Saves the object to a stream using a specific formatting.
        /// </summary>
        /// <param name="stream">Stream that will contain the serialized data of this object.</param>
        /// <param name="mode"><see cref="SerializationMode"/> to use.</param>
        public void Save(Stream stream, SerializationMode mode)
        {
            string stopwatchName = string.Format(CultureInfo.CurrentCulture, TraceMessages.SavingObject, GetType().Name, mode);
            Log.StartStopwatchTrace(stopwatchName);

            switch (mode)
            {
#if SILVERLIGHT
                case SerializationMode.JSON:
                    break;
#else
                case SerializationMode.Binary:
                    BinaryFormatter binaryFormatter = SerializationHelper.GetBinarySerializer(false);
                    binaryFormatter.Serialize(stream, this);
                    break;
#endif

                case SerializationMode.Xml:
                    XmlWriterSettings settings = new XmlWriterSettings();
                    settings.OmitXmlDeclaration = false;
                    settings.Indent = true;

                    using (XmlWriter xmlWriter = XmlWriter.Create(stream, settings))
                    {
                        xmlWriter.WriteStartElement(GetType().Name);
                        ((IXmlSerializable)this).WriteXml(xmlWriter);
                        xmlWriter.WriteEndElement();
                    }

                    break;
            }

            Log.StopStopwatchTrace(stopwatchName);

            ClearIsDirtyOnAllChilds();
        }
        #endregion

        #region Loading
        /// <summary>
        /// Loads the object from a file using binary formatting.
        /// </summary>
        /// <param name="fileName">Filename of the file that contains the serialized data of this object.</param>
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        public static T Load(string fileName)
        {
            return Load<T>(fileName, false);
        }

        /// <summary>
        /// Loads the object from a file using binary formatting.
        /// </summary>
        /// <param name="fileName">Filename of the file that contains the serialized data of this object.</param>
        /// <param name="enableRedirects">if set to <c>true</c>, redirects will be enabled.</param>
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        /// <remarks>
        /// When enableRedirects is enabled, loading will take more time. Only set
        /// the parameter to <c>true</c> when the deserialization without redirects fails.
        /// </remarks>
        public static T Load(string fileName, bool enableRedirects)
        {
            return Load<T>(fileName, enableRedirects);
        }

        /// <summary>
        /// Loads the object from a file using a specific formatting.
        /// </summary>
        /// <param name="fileName">Filename of the file that contains the serialized data of this object.</param>
        /// <param name="mode"><see cref="SerializationMode"/> to use.</param>
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        public static T Load(string fileName, SerializationMode mode)
        {
            return Load<T>(fileName, mode);
        }

        /// <summary>
        /// Loads the object from a file using a specific formatting.
        /// </summary>
        /// <param name="fileName">Filename of the file that contains the serialized data of this object.</param>
        /// <param name="mode"><see cref="SerializationMode"/> to use.</param>
        /// <param name="enableRedirects">if set to <c>true</c>, redirects will be enabled.</param>
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        /// <remarks>
        /// When enableRedirects is enabled, loading will take more time. Only set
        /// the parameter to <c>true</c> when the deserialization without redirects fails.
        /// </remarks>
        public static T Load(string fileName, SerializationMode mode, bool enableRedirects)
        {
            return Load<T>(fileName, mode, enableRedirects);
        }

        /// <summary>
        /// Loads the object from an XmlDocument object.
        /// </summary>
        /// <param name="xmlDocument">The XML document.</param>
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        public static T Load(XDocument xmlDocument)
        {
            return Load<T>(xmlDocument);
        }

        /// <summary>
        /// Loads the object from a stream using binary formatting.
        /// </summary>
        /// <param name="bytes">The byte array.</param>
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        public static T Load(byte[] bytes)
        {
            return Load<T>(bytes);
        }

        /// <summary>
        /// Loads the object from a stream.
        /// </summary>
        /// <param name="bytes">The byte array.</param>
        /// <param name="enableRedirects">if set to <c>true</c>, redirects will be enabled.</param>
        /// <returns>
        /// Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.
        /// </returns>
        /// <remarks>
        /// When enableRedirects is enabled, loading will take more time. Only set
        /// the parameter to <c>true</c> when the deserialization without redirects fails.
        /// </remarks>
        public static T Load(byte[] bytes, bool enableRedirects)
        {
            return Load<T>(bytes, enableRedirects);
        }

        /// <summary>
        /// Loads the object from a stream using binary formatting.
        /// </summary>
        /// <param name="stream">Stream that contains the serialized data of this object.</param>
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        public static T Load(Stream stream)
        {
            return Load<T>(stream);
        }

        /// <summary>
        /// Loads the specified stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="enableRedirects">if set to <c>true</c>, redirects will be enabled.</param>
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        /// <remarks>
        /// When enableRedirects is enabled, loading will take more time. Only set
        /// the parameter to <c>true</c> when the deserialization without redirects fails.
        /// </remarks>
        public static T Load(Stream stream, bool enableRedirects)
        {
            return Load<T>(stream, enableRedirects);
        }

        /// <summary>
        /// Loads the object from a stream using a specific formatting.
        /// </summary>
        /// <param name="stream">Stream that contains the serialized data of this object.</param>
        /// <param name="mode"><see cref="SerializationMode"/> to use.</param> 
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        public static T Load(Stream stream, SerializationMode mode)
        {
            return Load<T>(stream, mode);
        }

        /// <summary>
        /// Loads the object from a stream using a specific formatting.
        /// </summary>
        /// <param name="stream">Stream that contains the serialized data of this object.</param>
        /// <param name="mode"><see cref="SerializationMode"/> to use.</param> 
        /// <param name="enableRedirects">if set to <c>true</c>, redirects will be enabled.</param>
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        /// <remarks>
        /// When enableRedirects is enabled, loading will take more time. Only set
        /// the parameter to <c>true</c> when the deserialization without redirects fails.
        /// </remarks>
        public static T Load(Stream stream, SerializationMode mode, bool enableRedirects)
        {
            return Load<T>(stream, mode, enableRedirects);
        }
        #endregion
        #endregion

        #region IXmlSerializable Members
        /// <summary>
        /// This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> method.
        /// </returns>
        System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
        {
            // As requested by the documentation, we return null
            return null;
        }

        /// <summary>
        /// Generates an object from its XML representation.
        /// </summary>
        /// <param name="reader">The <see cref="T:System.Xml.XmlReader"/> stream from which the object is deserialized.</param>
        void IXmlSerializable.ReadXml(XmlReader reader)
        {
            if (reader.IsEmptyElement)
            {
            	return;
            }

            XmlReaderSettings settings = new XmlReaderSettings();
            settings.IgnoreComments = true;
            settings.IgnoreProcessingInstructions = true;
            settings.IgnoreWhitespace = true;
            XmlReader newReader = XmlReader.Create(reader, settings);

            if (!newReader.Read())
            {
            	return;
            }

            newReader.MoveToContent();

            XNode objectNode = XNode.ReadFrom(newReader);

            if (objectNode is XContainer)
            {
                foreach (XNode node in ((XContainer)objectNode).Nodes())
                {
                    if (node is XElement)
                    {
                        XElement element = (XElement)node;
                        string propertyName = MapXmlNameToPropertyName(element.Name.LocalName);

                        object value = GetObjectFromXmlNode(node, propertyName, element.Name.LocalName);
                        if (value != null)
                        {
                            _propertyValues[propertyName] = value;
                        }
                    }
                }
            }

            FinishDeserialization();
        }

        /// <summary>
        /// Gets the object from XML node.
        /// </summary>
        /// <param name="node">The node from which to get the object.</param>
        /// <param name="propertyName">Name of the property as known in the class.</param>
        /// <param name="xmlName">Name of the property as known in XML.</param>
        /// <returns>Object or <c>null</c>.</returns>
        private object GetObjectFromXmlNode(XNode node, string propertyName, string xmlName)
        {
            object value = null;

            if (IsPropertyRegistered(propertyName))
            {
                try
                {
#if SILVERLIGHT
                    DataContractSerializer serializer = SerializationHelper.GetDataContractSerializer(GetPropertyData(propertyName).Type, xmlName);
                    value = serializer.ReadObject(node.CreateReader());
#else
                    XmlSerializer serializer = SerializationHelper.GetXmlSerializer(GetPropertyData(propertyName).Type, xmlName);
                    value = serializer.Deserialize(node.CreateReader());
#endif
                }
                catch (Exception)
                {
                    Log.Error(TraceMessages.FailedToDeserializeProperty, propertyName, xmlName);
                }
            }

            return value;
        }

        /// <summary>
        /// Converts an object into its XML representation.
        /// </summary>
        /// <param name="writer">The <see cref="T:System.Xml.XmlWriter"/> stream to which the object is serialized.</param>
        void IXmlSerializable.WriteXml(XmlWriter writer)
        {
            foreach (KeyValuePair<string, object> propertyValue in _propertyValues)
            {
                PropertyInfo propertyInfo = GetPropertyInfo(propertyValue.Key);
                if (propertyInfo == null)
                {
                	continue;
                }

                XmlIgnoreAttribute xmlIgnoreAttribute;
                if (AttributeHelper.TryGetAttribute(propertyInfo, out xmlIgnoreAttribute))
                {
                	continue;
                }

                if (propertyValue.Value == null)
                {
                	continue;
                }

                string serializationPropertyName = MapPropertyNameToXmlName(propertyValue.Key);

#if SILVERLIGHT
                // Create serializer
                DataContractSerializer serializer = SerializationHelper.GetDataContractSerializer(propertyValue.Value.GetType(), serializationPropertyName);

                // Serialize
                serializer.WriteObject(writer, propertyValue.Value);
#else
                XmlSerializer serializer = SerializationHelper.GetXmlSerializer(propertyValue.Value.GetType(), serializationPropertyName);

                XmlSerializerNamespaces blankNamespaces = new XmlSerializerNamespaces();
                blankNamespaces.Add(string.Empty, string.Empty);

                serializer.Serialize(writer, propertyValue.Value, blankNamespaces);
#endif
            }
        }

        /// <summary>
        /// Maps the name of the XML element to a property name.
        /// </summary>
        /// <param name="xmlName">Name of the XML element.</param>
        /// <returns>Name of the property that represents the xml value.</returns>
        private string MapXmlNameToPropertyName(string xmlName)
        {
            return PropertyDataManager.MapXmlNameToPropertyName(GetType(), xmlName);
        }

        /// <summary>
        /// Maps the name of the property name to an XML name.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <returns>Name of the XML element that represents the property value.</returns>
        private string MapPropertyNameToXmlName(string propertyName)
        {
            return PropertyDataManager.MapPropertyNameToXmlName(GetType(), propertyName);
        }
        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Geert van Horrik
Software Developer CatenaLogic
Netherlands Netherlands
I am Geert van Horrik, and I have studied Computer Science in the Netherlands.
 
I love to write software using .NET (especially the combination of WPF and C#). I am also the lead developer of Catel, an open-source application development framework for WPF, Silverlight, WP7 and WinRT with the focus on MVVM.
 
I have my own company since January 1st 2007, called CatenaLogic. This company develops commercial and non-commercial software.
 
To download (or buy) applications I have written, visit my website: http://www.catenalogic.com
Follow on   Twitter

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 28 Jan 2011
Article Copyright 2011 by Geert van Horrik
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid