Quantcast
Channel: Knifecore
Viewing all articles
Browse latest Browse all 21

Sitecore unit testing with test fixtures

$
0
0

Unit testing logic that uses the Sitecore content API is not very easy. Alistair Deneys has written some very good articles on it here and here. The second article describes how to use mocking. I prefer that approach over testing on a running Sitecore website (integration testing), because I want my tests to be fast and I don’t like the risk of breaking my Sitecore website by messing up the database.

But mocking can be very tedious. Having to setup all the mocks for your tests can take a lot of time and that may be used as an excuse not to unit test your code at all. Setting up large test fixtures can be very time consuming as well.

Since I recently made a Sitecore DataProvider, I’ve gotten somewhat familiar with that concept. So now I created a DataProvider that works in-memory. On startup, it loads Sitecore items from sources like Sitecore packages and serialized data (should work with TDS items as well). That should make it much easier to create test fixtures with your own items and templates! After your tests have run, your changes will be lost. That way, your tests can never ‘harm’ any database.

I’ve shared the code here on GitHub (I wanted to try GitHub). Here are some steps to help you set things up:

  1. Download the project or pull it from GitHub.
  2. Make sure all the project references are working.
  3. Edit the SampleTestProject.dll.config file and change the absolute paths to correspond with the paths on your machine.
  4. Run the unit tests in SampleTestProject (they should pass).

There are three projects in the solution:

  1. FixtureDataProvider – This is the project that contains the DataProvider. If you inherit your unit testing class from SitecoreUnitTestBase, then some basic Sitecore stuff will be configured for you before the tests are executed.
  2. SampleSitecoreProject – This project contains some logic that utilizes the Sitecore content API. More specifically, it can read KML and makes Sitecore items for every Placemark (containing a description and the coordinates of the placemark):
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
            /// <summary>
            /// Takes KML XML input and creates some Sitecore items with the 'Placemark' template.
            /// </summary>
            /// <param name="kmlDocument"></param>
            /// <param name="timeStamp">Current date, to be used for the Sitecore folder name under which imported items are placed</param>
            public static void ImportKml(XDocument kmlDocument, DateTime timeStamp)
            {
                Item folderTemplate = Sitecore.Context.Database.GetItem(Sitecore.TemplateIDs.Folder);
                Item placemarkTemplate = Sitecore.Context.Database.GetItem("/sitecore/templates/User Defined/Kml/Placemark");
                Item contentRoot = Sitecore.Context.Database.GetItem(Sitecore.ItemIDs.ContentRoot);
    
                Item importRoot = contentRoot.Add(string.Format("Imported content {0}", timeStamp.ToString("yyyy MM dd")), new TemplateItem(folderTemplate));
    
                const string kmlNamespace = "http://www.opengis.net/kml/2.2";
    
                foreach (XElement placemarkElement in kmlDocument.Descendants(XName.Get("Placemark", kmlNamespace)))
                {
                    XElement name = placemarkElement.Element(XName.Get("name", kmlNamespace));
                    XElement description = placemarkElement.Element(XName.Get("description", kmlNamespace));
                    XElement point = placemarkElement.Element(XName.Get("Point", kmlNamespace));
    
                    if (name != null && ! string.IsNullOrWhiteSpace(name.Value))
                    {
                        Item placemarkItem = importRoot.Add(ItemUtil.ProposeValidItemName(name.Value.Replace(":","")), new TemplateItem(placemarkTemplate));
                        using (new EditContext(placemarkItem))
                        {
                            if (description != null && !string.IsNullOrWhiteSpace(description.Value))
                            {
                                placemarkItem["Description"] = description.Value;
                            }
                            if (point != null)
                            {
                                XElement coordinates = point.Element(XName.Get("coordinates", kmlNamespace));
                                if (coordinates != null && !string.IsNullOrWhiteSpace(coordinates.Value))
                                {
                                    string[] splitCoordinates = coordinates.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                                    if (splitCoordinates.Length > 0)
                                    {
                                        placemarkItem["Longitude"] = splitCoordinates[0];
                                    }
                                    if (splitCoordinates.Length > 1)
                                    {
                                        placemarkItem["Latitude"] = splitCoordinates[1];
                                    }
                                    if (splitCoordinates.Length > 2)
                                    {
                                        placemarkItem["Altitude"] = splitCoordinates[2];
                                    }
                                }
                            }
                        }
                    }
                }
            }

     

     

  3. SampleTestProject – This project contains the SampleTestProject.dll.config file, which is kind of like a very minimal Web.config (without the need for an actual website to be running). The project also contains a data folder that has some serialized items and a zip package (the test fixture data). And you’ll also find some KML files to be imported. The UnitTest class contains the test of the previous code:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
            [TestMethod]
            [Description("Tests the import of a KML file")]
            public void TestKmlImport()
            {
                string sampleKmlFile = ConfigurationManager.AppSettings["samplekml"];
                DateTime timeStamp = DateTime.Today;
                SampleSitecoreLogic.ImportKml(sampleKmlFile, timeStamp);
    
                // Check if the import root node is available
                Item imported = Sitecore.Context.Database.GetItem(string.Format("/sitecore/content/Imported content {0}", timeStamp.ToString("yyyy MM dd")));
    
                Assert.IsNotNull(imported, "Import root item could not be found");
    
                // Check if all 35 placemarks in the KML file were imported
                Assert.AreEqual(35, imported.Children.Count, "Not the right amount of imported placemarks found");
    
                // Check if all values are filled
                foreach (Item child in imported.Children)
                {
                    Assert.IsFalse(string.IsNullOrWhiteSpace(child["Description"]), string.Format("Description field empty for {0}", child.Paths.FullPath));
                    Assert.IsFalse(string.IsNullOrWhiteSpace(child["Longitude"]), string.Format("Longitude field empty for {0}", child.Paths.FullPath));
                    Assert.IsFalse(string.IsNullOrWhiteSpace(child["Latitude"]), string.Format("Latitude field empty for {0}", child.Paths.FullPath));
                    Assert.IsFalse(string.IsNullOrWhiteSpace(child["Altitude"]), string.Format("Altitude field empty for {0}", child.Paths.FullPath));
                }
    
                // Check the contents of an individual item
                Item muiderSlotItem = imported.Axes.GetChild("Muiderslot");
                Assert.IsNotNull(muiderSlotItem, "Castle 'Muiderslot' was not found after import");
                Assert.IsTrue(muiderSlotItem["Description"].Contains("Muiderslot<br>Country: Netherlands<br>Region: Noord Holland<br>Place: Muiden"),
                    "Castle 'Muiderslot' does not have a correct description");
                Assert.AreEqual("5.0718055556", muiderSlotItem["Longitude"]);
                Assert.AreEqual("52.33423055599999", muiderSlotItem["Latitude"]);
                Assert.AreEqual("0", muiderSlotItem["Altitude"]);
    
                Console.WriteLine(string.Format("=== SITECORE TREE STRUCTURE DUMP (for debugging) ==="));
                LogTreeStructure(Sitecore.Context.Database.GetRootItem());
            }

     

     

Some important points I want to make:

  • Events and cache are disabled by default. So if you need to test those aspects, you’ll have to do some extra work.
  • Though I attempted to emulate the SQL DataProvider as much as possible, I can not guarantee that it behaves in the exact same way at all times. Remember that unit testing can never replace integration testing and functional testing altogether.
  • I have not yet implemented support for media (I might add support for that later, and for the MongoDB DataProvider as well). – Implemented (not yet for the MongoDB DataProvider)

Happy testing! And let me know if you have any questions or ideas!


Viewing all articles
Browse latest Browse all 21

Trending Articles