Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating unit tests for assuring immutability

I have designed an immutable class, because I want to have value-semantics for it. I wrote a hint into the commentary section of the class

// "This class is immutable, don't change this when adding new features to it."

But I know, sometimes those commentaries are overlooked by other team members, so I would like to create a unit test as an additional safeguard. Any idea how to accomplish this? Can one inspect a class via reflection to make sure only the constructors change it's inner state?

(Using C# 2.0 and NUnit, if that's important for anyone).

like image 570
Doc Brown Avatar asked Mar 24 '11 09:03

Doc Brown


People also ask

Is immutable code is easy to test?

Easier testing and debugging - Since the state of your immutable object doesn't change, it's much easier to test your code, and track down bugs.

Why is it important for unit tests to run quickly?

The speed of detecting non-working code depends on the tools used for continuous integration. Tests can be set to run either a one-time check at a certain time interval or can be run immediately in real-time to review changes. In short, unit tests help developers detect problems immediately, then fix them quickly.


2 Answers

An example to back up my comment on how you can use FieldInfo.IsInitOnly recursively to test for immutability.

There may be more special cases to consider like how I have handled string, but it will only give false negatives I believe, i.e. will tell you something is mutable that is not, not the other way around.

The logic is, every field must be readonly and be an immutable type itself. Note that it will not cope with self referential types or circular references.

using System;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace ImmutableTests
{
    [TestClass]
    public class AssertImmutableTests
    {
        [TestMethod]
        public void Is_int_immutable()
        {
            Assert.IsTrue(Immutable<int>());
        }

        [TestMethod]
        public void Is_string_immutable()
        {
            Assert.IsTrue(Immutable<string>());
        }

        [TestMethod]
        public void Is_custom_immutable()
        {
            Assert.IsTrue(Immutable<MyImmutableClass>());
        }

        [TestMethod]
        public void Is_custom_mutable()
        {
            Assert.IsFalse(Immutable<MyMutableClass>());
        }

        [TestMethod]
        public void Is_custom_deep_mutable()
        {
            Assert.IsFalse(Immutable<MyDeepMutableClass>());
        }

        [TestMethod]
        public void Is_custom_deep_immutable()
        {
            Assert.IsTrue(Immutable<MyDeepImmutableClass>());
        }

        [TestMethod]
        public void Is_propertied_class_mutable()
        {
            Assert.IsFalse(Immutable<MyMutableClassWithProperty>());
        }

        private static bool Immutable<T>()
        {
            return Immutable(typeof(T));
        }

        private static bool Immutable(Type type)
        {
            if (type.IsPrimitive) return true;
            if (type == typeof(string)) return true;
            var fieldInfos = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            var isShallowImmutable = fieldInfos.All(f => f.IsInitOnly);
            if (!isShallowImmutable) return false;
            var isDeepImmutable = fieldInfos.All(f => Immutable(f.FieldType));
            return isDeepImmutable;
        }
    }

    public class MyMutableClass
    {
        private string _field;
    }

    public class MyImmutableClass
    {
        private readonly string _field;
    }

    public class MyDeepMutableClass
    {
        private readonly MyMutableClass _field;
    }

    public class MyDeepImmutableClass
    {
        private readonly MyImmutableClass _field;
    }

    public class MyMutableClassWithProperty
    {
        public string Prop { get; set; }
    }
}
like image 194
weston Avatar answered Sep 27 '22 03:09

weston


You could check that the class is sealed, and using reflection check that each field is read-only (using FieldInfo.IsInitOnly).

Of course, that only ensures shallow immutability - it wouldn't stop someone from putting a List<int> field in there, and then changing the contents of the list.

like image 35
Jon Skeet Avatar answered Sep 26 '22 03:09

Jon Skeet