How can you leverage Unit Testing when scripting with Content Hub?


For some people, writing code is a job, for others like me, writing code is an art form. It is a way of expressing myself in the code. Like every book author leaves his/her mark on a book, so does a developer on his code. The way a person writes the code will tell you a lot about his/her personality.

There is one book that I would really like to recommend for you to read if you haven't already read it, and that is Clean Code. The author of the book is Robert C. Martin. I really like the way he explains the way he thinks how you should be writing your code. Here's a preview of the book.


You might be thinking, "Ok, nice book, but how about developing with Sitecore?". Let's continue to writing scripts in Content Hub. The last time I wrote about using the CLI to sync your script to your local machine. This blog post will continue from that point. So if you haven't read it yet, please do so.

The last time I synced the CMP - Create public links for linked assets.csx script file. Today we're going to take a look at how to make the script unit testable and improve it in the process. The first thing we need to have is a file to place our unit tests in.
  1. In Visual Studio Code, create a new file name 
    CMP - Create public links for linked assets#UnitTests.csx

  2. Now that we have a file, we need to add a reference to the actual script. Add the following line at the top of the file
    #load "CMP - Create public links for linked assets.csx"
    

  3. The cool thing we can do now, is actually running the Unit Test script via dotnet. Execute the following command in a VS Code terminal
    dotnet script '.\CMP - Create public links for linked assets#UnitTests.csx'

  4. As you can see, this will utterly fail at this moment
    Visual Studio Code - Run unit test

  5. There are a couple of different errors in the output that we can spot. There are two files that we need to update before we now can continue. The two files are Action.csx and CMP - Create public links for linked assets.csx

  6. Let us first fix the Action.csx. Open the file in Visual Studio Code. You can find the file in the references folder. When you open the file you see that it contains two properties. MClient and Context. The error that we save before, is that the code is missing the usings in this file. Add the following usings below the #load
    using Stylelabs.M.Sdk;
    using Stylelabs.M.Scripting.Types.V1_0.Action;

  7. Save the file and run the dotnet command again.
    dotnet script '.\CMP - Create public links for linked assets#UnitTests.csx'

    Visual Studio Code - Updated Actions

  8. Now that we've solved the Action.csx issues we can continue working on the CMP - Create public links for linked assets.csx file. In order to be able to build the script locally, we need to add the proper usings. Open the file and make sure that you've the following usings:
    using System.Linq;
    using System.Threading.Tasks;
    using Stylelabs.M.Base.Querying;
    using Stylelabs.M.Base.Querying.Linq;
    using Stylelabs.M.Framework.Essentials.LoadOptions;
    using Stylelabs.M.Scripting.Types.V1_0.Action;
    using Stylelabs.M.Sdk;
    using Stylelabs.M.Sdk.Contracts.Base;
    

  9. After that has been fixed, we can now start working on making the code testable. The first thing we will do is wrap the code inside a public static method and a simple check to see if the mClient and context are available. Like so:
    public static async Task RunScriptAsync(IMClient mClient, IActionScriptContext context)
    {
        if(mClient == null || context == null)
        {
            return;
        }
        
        // existing code
    }
    

  10. Above the method we will need to add the reference to execute the method:
    await RunScriptAsync(MClient, Context);
    

  11. After this we can start changing the reference from MClient to mClient and Context to context. This is something we need to make it testable when we write our unit tests.

  12. The script is reading for its unit test. Open the CMP - Create public links for linked assets#UnitTests.csx file in Visual Studio Code. Make sure to add the following references. We will need them in order to run our unit tests.
    #r "nuget:FluentAssertions, 6.1.0"
    #r "nuget: Moq, 4.16.1"
    #r "nuget: Newtonsoft.Json, 12.0.3"
    #r "nuget: Remotion.Linq, 2.2.0"
    #r "nuget: Stylelabs.M.Scripting.Types, *"
    #r "nuget: Stylelabs.M.Sdk, *"
    
    #load "nuget:ScriptUnit, 0.2.0"
    #load "CMP - Create public links for linked assets.csx"
    

  13. Let's take a short look to the references that we've added. Perhaps you already know a couple of them. But just assume that you don't. I will add a short explanation per reference.
    1. FluentAssertions
      A very extensive set of extension methods that allow you to more naturally specify the expected outcome of a TDD or BDD-style unit tests. Targets .NET Framework 4.7, as well as .NET Core 2.1, .NET Core 3.0, .NET Standard 2.0 and 2.1.
    2. Moq
      Moq is intended to be simple to use, strongly typed (no magic strings!, and therefore full compiler-verified and refactoring-friendly) and minimalistic (while still fully functional!).
    3. Newtonsoft.Json
      Popular high-performance JSON framework for .NET
    4. Remotion.Linq
      re-linq Frontend: A foundation for parsing LINQ expression trees and generating queries in SQL or other languages.
    5. Stylelabs.M
      Stylelabs references

  14. Add the lines of code to the page.
    using System.Collections.Generic;
    using System.Runtime;
    using System.Threading.Tasks;
    
    using static ScriptUnit;
    using FluentAssertions;
    using Moq;
    
    using Stylelabs.M.Base.Querying;
    using Stylelabs.M.Scripting.Types.V1_0.Action;
    using Stylelabs.M.Sdk;
    using Stylelabs.M.Sdk.Clients;
    using Stylelabs.M.Sdk.Contracts.Base;
    using Stylelabs.M.Sdk.Contracts.Logging;
    using Stylelabs.M.Sdk.Contracts.Querying;
    using Stylelabs.M.Sdk.Factories;
    
    Console.Clear();
    return await AddTestsFrom<CreatePublicLinkAssets>().Execute();
    
    public class CreatePublicLinkAssets
    {
        private const long EntityId = 1337;
        private readonly Mock<Stylelabs.M.Sdk.IMClient> _mclientMock;
        private readonly Mock<IActionScriptContext> _contextMock;
        private readonly Mock<ILogger> _loggerMock;
        private readonly Mock<IQueryingClient> _queryingClientMock;
        private readonly Mock<IEntityFactory> _entityFactoryMock;
    
        public CreatePublicLinkAssets(){
            _contextMock = new Mock<IActionScriptContext>();
            _loggerMock = new Mock<ILogger>();
            _mclientMock = new Mock<IMClient>();
            _queryingClientMock = new Mock<IQueryingClient>();
            _entityFactoryMock = new Mock<IEntityFactory>();
    
            _mclientMock.Setup(x => x.Querying).Returns(_queryingClientMock.Object);
            _mclientMock.Setup(x => x.Logger).Returns(_loggerMock.Object);
            _contextMock.Setup(x => x.TargetId).Returns(EntityId);
            _mclientMock.Setup(x => x.EntityFactory).Returns(_entityFactoryMock.Object);
        }
    
        public async void ExecuteScript_ShouldNotThrowExceptions()
        {
            // arrange
            Exception caughtExcetion = null;
    
            var queryResult = new FakeIdQueryResult { Items = new List<long> { EntityId } };
             _queryingClientMock.Setup(x => x.QueryIdsAsync(It.IsAny<Query>()))
                    .ReturnsAsync(queryResult);
    
            _entityFactoryMock.Setup(x => x.CreateAsync("M.PublicLink", null))
                .ReturnsAsync(new Mock<IEntity>().Object);
    
            // act
            try{
                await RunScriptAsync(_mclientMock.Object, _contextMock.Object);
            }
            catch(Exception ex)
            {
                caughtExcetion = ex;
            }
            
            // assert
            caughtExcetion.Should().BeNull();
        }
    
        internal class FakeIdQueryResult : IIdQueryResult
        {
            public IList<long> Items {get;set;}
            public long TotalNumberOfResults => Items.Count;
            public long Offset { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
            IList<object> IQueryResult.Items => throw new NotImplementedException();
    
            public IIdIterator CreateIterator()
            {
                throw new NotImplementedException();
            }
        }
    }
    

  15. Save your changes to the file. Let try to run our unit test! Open a terminal in Visual Studio Code and execute the following command:
    dotnet script '.\CMP - Create public links for linked assets#UnitTests.csx'

  16. If all went well, it should look similar to this

    dotnet script '.\CMP - Create public links for linked assets#UnitTests.csx'

  17. If you don't have the complete code. Don't worry, I've got you covered. Check out the GitHub project. It has all the files that you need.

  18. When you're happy with your code and the way it behaves, we can sync it back to our Content Hub instance. Execute the command below.
    ch-cli preview scripting --watch

  19. Last but not least, we need to activate the trigger watch. Open the CMP - Create public links for linked assets.csx file. Make a small change and save the file.

  20. When the trigger runs, it should look like this
    Trigger watch

I hope that this gives you a better understanding of how to use Unit Testing with scripting in Content Hub. In a future blog post, we're going to take a look at how to incorporate it in Azure DevOps.