8

I'm trying to find the correct approach to begin unit testing the piece of code below:

public class NameUtility
{
    private readonly string _folder = Settings.GetSetting("FolderId");
    public ISitecoreDatabase Database {get;set;} = new SitecoreDatabase();

    public virtual Path FindById(long Id)
    {
        var folder = Database.GetItem(new ID(_folder));
        var items = folder.GetChildren().Select(child => child.GlassCast<Path>());
        return paths.SingleOrDefault(path => path.PathId == Id);
    }
}

I'm quite new to unit testing so I chose the simplest utility I could find in order to test. This is the start of what my unit test looks like:

[TestFixture]
public class NameUtilityTests
{
    [Test]
    public void FindById_WithId_ReturnsPath()
    {
        var nameUtility = new NameUtility();
        nameUtility.Database = new FakeSitecoreDatabase();

        var path = nameUtility.FindById(1);

        Assert.AreEqual(1, path.PathId);
    }

    internal class FakeSitecoreDatabase : ISitecoreDatabase
    {
        public Item GetItem(ID id)
        {
            throw new System.NotImplementedException();
        }
    }
}

//Code below is from the Code Under Test assembly.
public interface ISitecoreDatabase
{
    Item GetItem(ID id);
}

public class SitecoreDatabase : ISitecoreDatabase
{
    private readonly Database _database;

    public SitecoreDatabase()
    {
        _database = Sitecore.Context.Database;
    }

    public SitecoreDatabase(Database database)
    {
        _database = database;
    }

    public Item GetItem(ID id)
    {
        return _database.GetItem(id);
    }
}

So I have a couple of questions and I'll explain my thought process first. FindById currently depends on Sitecore.Context.Database which wouldn't work for Unit Tests so we've used SitecoreFakeDb instead. I haven't implemented the GetItem method, but the idea would be that I setup an instance of SitecoreFakeDb with the item(s) and then the CUT should be able to fetch the item by id.

1) However to test this code I need to be able to substitute Sitecore.Context.Database with an instance of the FakeDb. For this, I've created ISitecoreDatabase and then it has methods like GetItem() and all its overloads that the default (actual) Sitecore db would implement as well as the FakeDb. Wouldn't this begin to get out of control as far as how many methods that interface would have?

2) When asserting against the final object, in this case I'm asserting that the object returned has a field with the same Id as what I expect. Is this a valid approach, or is the better approach to compare the entire actual object with an expected object?

  • You also have the option of testing using Moq. The benefit to using this, is it will allow you to have a common testing approach to both Sitecore testing in your solution and C# testing. You can see the answer I posted to your question in StackOverflow for an example stackoverflow.com/questions/40809986/…. – C Sigmon Nov 26 '16 at 20:11
12

Answers

1) Yes, that will get out of control. You are actually implementing your own implementation for faking the Sitecore database. There already exists a solution for this: https://github.com/sergeyshushlyapin/Sitecore.FakeDb.
I really recommend using this. From the github page:

This is the unit testing framework for Sitecore that enables creation and manipulation of Sitecore content in memory. It is designed to minimize efforts for the test content initialization keeping focus on the minimal test data rather than comprehensive content tree representation.

edit: added extra information on how FakeDb works:

FakeDb substitutes Sitecore providers with fake "in-memory" providers to avoid calls to database or configuration. The providers do not replicate real Sitecore behavior, but are used mainly as stubs with minimal logic. They can be replaced with mocks in unit tests using the provider switchers.
So you can just write your code like you want it to. And don't have to implement an interface.

2) Your assert is ok. I don't know what type PathId is. If it is an int then its definitely ok.

Important note

You are using a Setting called FolderId. For your unit tests to work you must either:
1) add your configuration to the App_Config folder of your unit test project which could be a hassle to maintain configs in your actual website project and your unit test project.
2) mock the setting as shown in the code example below.

General tips:

When Unit Testing for Sitecore, I would recommend starting with these packages to make your life a little bit easier:

The listed packages can all be installed through Nuget.
Also, instead of referencing Sitecore dll files directly, you could use Nuget to fetch the package with the depenencies you need.
You can read more about this on the Sitecore Documentation site: https://doc.sitecore.net/sitecore_experience_platform/developing/developing_with_sitecore/sitecore_public_nuget_packages_faq

I create my unit tests with the AAA pattern:

  • Arrange: setup, initialize everything
  • Act: invoke method(s)
  • Assert: validate result

Example code

public class NameUtility
{
    private readonly string _folder = Settings.GetSetting("FolderId");

    public virtual Path FindById(long id)
    {
        var folder = Database.GetItem(new ID(_folder));
        var paths = folder.GetChildren().Select(child => child.GlassCast<Path>());
        return paths.SingleOrDefault(path => path.PathId == id);
    }
}

[TestClass]
public class NameUtilityTests
{
    //XUnit Fact attribute
    [Fact]
    public void FindById_WithId_ReturnsPath()
    {
        //Create your database for the test with the Sitecore.FakeDb.Db class
        using (var db = new Db
        {
            //Arrange
            //create an item with a name (and optionally an id and template id)
            new DbItem("FolderName")
            { 
                //you can set specific fields for the item, all values are strings.
                //You'll need to set the PathId here!
                { "Title", "Testing the Id" },
                { "PathId", "123456789" }
            }
        })
        {
            //Setup configuration
            db.Configuration.Settings["FolderId"] = Guid.NewGuid().ToString();
            //Create instance
            var sut = new NameUtility();
            //Act
            sut.FindById(123456789);
            //Assert
            Xunit.Assert.Equal(sut.PathId, 123456789);
        }
    }
}

Possible additional improvements

  • rename method to FindByPathId: you are searching by PathId, not just an id
  • folder variable could be null and your code will fail when calling GetChildren. You can write a unit test for this.
  • SingleOrDefault could return null. You can write a unit test for this.

Hope this helps!

  • Thank you for the detailed explanation. In the sample code you provided you call Database.GetItem() in the FindById method. How would you distinguish between using Sitecore.Context.Database.GetItem and the FakeDb? I had an interface there for the purpose of creating those FakeDb items you've created and then passing in the FakeDb to the CUT. But that would present maintainability problems as you've mentioned. As it turns out, I don't need to mock the folder Id because I've actually included the configs in the unit test project. – uioporqwerty Nov 26 '16 at 1:57
  • That is the beauty of FakeDB, you can just write your code using Sitecore.Context.Database. FakeDB takes care that you don't access the actual database. I'll update my answer a bit – Koen Heye Nov 26 '16 at 8:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.