Home > Back-end >  Unit test singletons
Unit test singletons

Time:10-09

I have a singleton class and I want to run some unit tests in isolation. The problem is that all the tests run on the same instance and affect the expected results. Is there a workaround? I am using .net 6 and NUnit 3. Here's my code,

        public interface ISingletonModel<T> 
        {
            void AddValue(string key, T value, LinkedListNode node);
        }
        public sealed class SingletonModel<T> : ISingletonModel<T>
        {
            public Dictionary<string, (LinkedListNode<string> node, T value)> ModelDictionary { get; set; }
    
            private static readonly Lazy<SingletonModel<T>> singletonModel=
                new Lazy<SingletonModel<T>>(() => new SingletonModel<T>());
    
            public static SingletonModel<T> Instance
            {
                get
                {
                    return SingletonModel.Value;
                }
            }
    
            private SingletonModel()
            {
                ModelDictionary = new Dictionary<string, (node, T value)>();
            }  
        }


        public void AddValue(string key, T value, LinkedListNode node)
        {
            if (ModelDictionary.ContainsKey(key))
            {
                 var linkedListNode = ModelDictionary[key];
                 ModelDictionary[key] = (node, value);
            }
            else
            {
                ModelDictionary.Add(key, (node.AddFirst(key), value));
            }
        }
   }

And some unit tests

 private TestClass[] testData;
    private IFixture fixture;
    private SingletonModel<TestClass> model;

    [SetUp]
    public void Setup()
    {
        this.testData = GenerateTestData();
        this.model= SingletonModel<TestClass>.Instance;
    }

    [Test]
    public void CheckModelCapacity_ShouldReturnTheCorrectItems()
    {
         / Act
        foreach (var entity in testData)
        {
            this.model.AddValue(entity.Id, entity, entity.node);
        }

        IEnumerable<string> expected = new[] { "8", "3", "5", "2", "79" };

        var actual = this.model.ModelDictionary.Keys.ToList();

        // Assert
        Assert.That(expected.OrderBy(x => x).SequenceEqual(actual.OrderBy(x => x)));
    }

 [Test]
    public void CheckTotalItems_ShouldReplaceItemsWithTheSameKey()
    {
        // Assign
        var entity1 = fixture.CreateMany(20);

        // Act
        foreach (var item in entity1)
        {
            this.model.AddValue(item.Id, item, item.node);
        }

        //Assert
        Assert.AreEqual(2, this.model.ModelDictionary.Count);
    }

Because of the singleton the tests are holding the values from the previous tests.

CodePudding user response:

A public or internal constructor would allow for creating isolated instances in tests, but if the intention here is to not modify the current class then reflection can be used to access the private constructor to create isolated instances for tests.

Here is a simplified model based on the original since it was incomplete and wouldn't compile.

public interface ISingletonModel<T> {
    void AddValue(string key, T value);
    int Count { get; }
}

public sealed class SingletonModel<T> : ISingletonModel<T> {
    Dictionary<string, T> dictionary;
    private static readonly Lazy<SingletonModel<T>> singletonModel = new Lazy<SingletonModel<T>>(() => new SingletonModel<T>());
    public static SingletonModel<T> Instance => singletonModel.Value;

    private SingletonModel() {
        dictionary = new Dictionary<string, T>();
    }

    public void AddValue(string key, T value) => dictionary[key] = value;
    public int Count => dictionary.Count;
}

Using one of the overloads of Activator.CreateInstance:

Activator.CreateInstance(Type type, bool nonPublic)

which uses reflection to access the private constructor, shows that instances can be created to be tested in isolation

public class Program {
    public static void Main() {
        //Arrange
        int expected = 1;
        ISingletonModel<int> model1 = (ISingletonModel<int>)Activator.CreateInstance(typeof(SingletonModel<int>), true);
        //Act
        model1.AddValue("one", 1);
        //Assert
        int actual = model1.Count;
        Console.WriteLine($"{actual}, As expected: {actual == expected}");
        
        //Arrange
        expected = 3;
        ISingletonModel<int> model2 = (ISingletonModel<int>)Activator.CreateInstance(typeof(SingletonModel<int>), true);
        //Act
        model2.AddValue("one", 1);
        model2.AddValue("two", 2);
        model2.AddValue("three", 3);
        //Assert        
        actual = model2.Count;
        Console.WriteLine($"{actual}, As expected: {actual == expected}");
        
        //Arrange
        expected = 2;
        ISingletonModel<int> model3 = (ISingletonModel<int>)Activator.CreateInstance(typeof(SingletonModel<int>), true);
        //Act
        model3.AddValue("one", 1);
        model3.AddValue("two", 2);
        //Assert
        actual = model3.Count;
        Console.WriteLine($"{actual}, As expected: {actual == expected}");
    }
}

which return the following output

1, As expected: True
3, As expected: True
2, As expected: True

CodePudding user response:

Add ResetForTesting() method your singleton class?

  • Related