Dc45352908dbb9a4c9299c1ffe84ebf2

I am doing a step by step example of TDD'ing towards the singleton pattern. The step by step Red, Green, Refactor cycle is at http://blog.sonerdy.com/2009/02/testing-into-singleton-pattern.html I am wondering if anyone has a more elegant way of testing that the object can only be instantiated by the factory method. This example uses an assertion that expects an exception when trying to new up the object the normal way. I haven't gone as far as to make sure this is thread safe. It's more of an excercise in Test-Driven development, and working towards a pattern. The idea of the ActiveUsers class is that it will have a list of the current users of the program. It's just a simple problem.

using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace TestingIntoSingleton
{
    [TestClass]
    public class TestActiveUsers
    {
        [TestMethod]
        public void Should_List_Logged_In_Users()
        {
            ActiveUsers UserManager = ActiveUsers.GetActiveUsers();
            UserManager.Add("Brandon");
            UserManager.Add("Amy");
            UserManager.Add("Jack");
            
            List<string> ExpectedUsers = new List<string>();
            ExpectedUsers.Add("Brandon");
            ExpectedUsers.Add("Amy");
            ExpectedUsers.Add("Jack");            

            foreach (var ExpectedUser in ExpectedUsers)
            {
                Assert.IsTrue( UserManager.Users.Contains(ExpectedUser));    
            }
        }

        [TestMethod]
        public void Should_Have_Only_One_Instance_Of_ActiveUsers()
        {
            ActiveUsers UserManager1 = ActiveUsers.GetActiveUsers();
            ActiveUsers UserManager2 = ActiveUsers.GetActiveUsers();
         
            UserManager1.Add("User");
            Assert.IsTrue(UserManager2.Users.Contains("User"));
        }

        [TestMethod]
        public void Should_Not_Allow_New_Instances()
        {
            try
            {
                ActiveUsers UserManager = new ActiveUsers();
                //We should not get this far
                Assert.Fail();
            }
            catch (System.Exception ex)
            {
                Assert.AreEqual("This object must be instantiated by the GetActiveUsers method", ex.Message);                 
            }
        }
    }

    public class ActiveUsers
    {
        private static ActiveUsers _instance;
        public List<string> Users;

        public ActiveUsers()
        {
            throw new System.Exception("This object must be instantiated by the GetActiveUsers method");            
        }

        private ActiveUsers(bool YouAreASingleton)
        {
            Users = new List<string>();
        }

        public static ActiveUsers GetActiveUsers()
        {        
            if (_instance == null)
            {
                _instance = new ActiveUsers(true);
            }
            return _instance;
        }

        public void Add(string User)
        {
            Users.Add(User);
        }
    }   
}

Refactorings

No refactoring yet !

Dc45352908dbb9a4c9299c1ffe84ebf2

Brandon Joyce

February 10, 2009, February 10, 2009 04:25, permalink

No rating. Login to rate!

I think this is better. Using reflection now on the third test to make sure that the constructor is private. This looks much cleaner.

using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Reflection;

namespace TestingIntoSingleton
{
    [TestClass]
    public class TestActiveSessions
    {
        [TestMethod]
        public void Should_List_Logged_In_Users()
        {
            ActiveUsers UserManager = ActiveUsers.GetActiveUsers();
            UserManager.Add("Brandon");
            UserManager.Add("Amy");
            UserManager.Add("Jack");
            
            List<string> ExpectedUsers = new List<string>();
            ExpectedUsers.Add("Brandon");
            ExpectedUsers.Add("Amy");
            ExpectedUsers.Add("Jack");            

            foreach (var ExpectedUser in ExpectedUsers)
            {
                Assert.IsTrue( UserManager.Users.Contains(ExpectedUser));    
            }
        }

        [TestMethod]
        public void Should_Have_Only_One_Instance_Of_ActiveUsers()
        {
            ActiveUsers UserManager1 = ActiveUsers.GetActiveUsers();
            ActiveUsers UserManager2 = ActiveUsers.GetActiveUsers();
         
            UserManager1.Add("User");
            Assert.IsTrue(UserManager2.Users.Contains("User"));
        }        

        [TestMethod]
        public void Should_Not_Allow_New_Instances()
        {           
            ConstructorInfo ci =
            typeof(ActiveUsers).GetConstructor(
                BindingFlags.Instance |
                BindingFlags.NonPublic,
                null,
                System.Type.EmptyTypes,
                null
                );
            Assert.IsNotNull(ci);         
        }
    }

    public class ActiveUsers
    {
        private static ActiveUsers _instance;
        public List<string> Users;     

        private ActiveUsers()
        {
            Users = new List<string>();
        }

        public static ActiveUsers GetActiveUsers()
        {        
            if (_instance == null)
            {
                _instance = new ActiveUsers();
            }
            return _instance;
        }

        public void Add(string User)
        {
            Users.Add(User);
        }
    }   
}
B2e94d208944667d82f5d707e0bda756

Quiz

February 16, 2009, February 16, 2009 11:27, permalink

No rating. Login to rate!

It's lazy evaluated too. ;)

public class ActiveUsers
{
    private static readonly ActiveUsers _instance = new ActiveUsers();
    public readonly List<string> Users = new List<string>();    

    public void Add(string User)
    {
        Users.Add(User);
    }
}
Dc45352908dbb9a4c9299c1ffe84ebf2

Brandon Joyce

February 25, 2009, February 25, 2009 01:23, permalink

No rating. Login to rate!

Cool. Got a test for that?

69f4fbbfa3d8fcef2e6d11f04db64c5b

Jonathan Starr

March 13, 2009, March 13, 2009 20:44, permalink

No rating. Login to rate!

How about this test?

[TestMethod]
        [ExpectedException(typeof(MissingMethodException))]
        public void TestCannotBeConstructed()
        {
          object result = Activator.CreateInstance(typeof(ActiveUsers));
        }
Dc45352908dbb9a4c9299c1ffe84ebf2

Brandon Joyce

March 14, 2009, March 14, 2009 00:48, permalink

No rating. Login to rate!

Cool. That must be NUnit. It takes the same approach with exceptions as my original, but is much cleaner. Thanks!

Your refactoring





Format Copy from initial code

or Cancel