UC Davis College of Agricultural & Environmental Sciences
Personal tools

Adding Unit Tests from a ReSharper Template

by Jason Sylvestre — last modified Apr 15, 2010 11:27 AM

Many tests are very similar to others structurally and using a ReSharper Template is one way to create many tests with a minimal amount of effort

As unit tests are added, often we find that many of the tests have a similar structure, just with a few different values. Once we have identified a group of tests like this we can create a ReSharper template to add the tests while prompting us to enter the values that are different, reusing those values throughout the tests so that they only have to be entered once.

If we look at the following set of tests we can see we are testing the FullName of the Unit record for the following things:

  1. That it doesn't save when the FullName is
    1. Null
    2. Empty
    3. Spaces only
    4. Exceeds the maximum length of 50 characters
  2. And that it does save when the FullName is
    1. One character long
    2. 50 characters long (the maximum length)
        #region FullName Tests
        #region Invalid Tests

        /// <summary>
        /// Tests the FullName with null value does not save.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ApplicationException))]
        public void TestFullNameWithNullValueDoesNotSave()
        {
            Unit unit = null;
            try
            {
                #region Arrange
                unit = GetValid(9);
                unit.FullName = null;
                #endregion Arrange

                #region Act
                UnitRepository.DbContext.BeginTransaction();
                UnitRepository.EnsurePersistent(unit);
                UnitRepository.DbContext.CommitTransaction();
                #endregion Act
            }
            catch (Exception)
            {
                Assert.IsNotNull(unit);
                var results = unit.ValidationResults().AsMessageList();
                results.AssertErrorsAre("FullName: may not be null or empty");
                Assert.IsTrue(unit.IsTransient());
                Assert.IsFalse(unit.IsValid());
                throw;
            }
        }

        /// <summary>
        /// Tests the FullName with empty string does not save.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ApplicationException))]
        public void TestFullNameWithEmptyStringDoesNotSave()
        {
            Unit unit = null;
            try
            {
                #region Arrange
                unit = GetValid(9);
                unit.FullName = string.Empty;
                #endregion Arrange

                #region Act
                UnitRepository.DbContext.BeginTransaction();
                UnitRepository.EnsurePersistent(unit);
                UnitRepository.DbContext.CommitTransaction();
                #endregion Act
            }
            catch (Exception)
            {
                Assert.IsNotNull(unit);
                var results = unit.ValidationResults().AsMessageList();
                results.AssertErrorsAre("FullName: may not be null or empty");
                Assert.IsTrue(unit.IsTransient());
                Assert.IsFalse(unit.IsValid());
                throw;
            }
        }

        /// <summary>
        /// Tests the FullName with spaces only does not save.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ApplicationException))]
        public void TestFullNameWithSpacesOnlyDoesNotSave()
        {
            Unit unit = null;
            try
            {
                #region Arrange
                unit = GetValid(9);
                unit.FullName = " ";
                #endregion Arrange

                #region Act
                UnitRepository.DbContext.BeginTransaction();
                UnitRepository.EnsurePersistent(unit);
                UnitRepository.DbContext.CommitTransaction();
                #endregion Act
            }
            catch (Exception)
            {
                Assert.IsNotNull(unit);
                var results = unit.ValidationResults().AsMessageList();
                results.AssertErrorsAre("FullName: may not be null or empty");
                Assert.IsTrue(unit.IsTransient());
                Assert.IsFalse(unit.IsValid());
                throw;
            }
        }

        /// <summary>
        /// Tests the FullName with too long value does not save.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ApplicationException))]
        public void TestFullNameWithTooLongValueDoesNotSave()
        {
            Unit unit = null;
            try
            {
                #region Arrange
                unit = GetValid(9);
                unit.FullName = "x".RepeatTimes((50 + 1));
                #endregion Arrange

                #region Act
                UnitRepository.DbContext.BeginTransaction();
                UnitRepository.EnsurePersistent(unit);
                UnitRepository.DbContext.CommitTransaction();
                #endregion Act
            }
            catch (Exception)
            {
                Assert.IsNotNull(unit);
                Assert.AreEqual(50 + 1, unit.FullName.Length);
                var results = unit.ValidationResults().AsMessageList();
                results.AssertErrorsAre("FullName: length must be between 0 and 50");
                Assert.IsTrue(unit.IsTransient());
                Assert.IsFalse(unit.IsValid());
                throw;
            }
        }
        #endregion Invalid Tests

        #region Valid Tests

        /// <summary>
        /// Tests the FullName with one character saves.
        /// </summary>
        [TestMethod]
        public void TestFullNameWithOneCharacterSaves()
        {
            #region Arrange
            var unit = GetValid(9);
            unit.FullName = "x";
            #endregion Arrange

            #region Act
            UnitRepository.DbContext.BeginTransaction();
            UnitRepository.EnsurePersistent(unit);
            UnitRepository.DbContext.CommitTransaction();
            #endregion Act

            #region Assert
            Assert.IsFalse(unit.IsTransient());
            Assert.IsTrue(unit.IsValid());
            #endregion Assert
        }

        /// <summary>
        /// Tests the FullName with long value saves.
        /// </summary>
        [TestMethod]
        public void TestFullNameWithLongValueSaves()
        {
            #region Arrange
            var unit = GetValid(9);
            unit.FullName = "x".RepeatTimes(50);
            #endregion Arrange

            #region Act
            UnitRepository.DbContext.BeginTransaction();
            UnitRepository.EnsurePersistent(unit);
            UnitRepository.DbContext.CommitTransaction();
            #endregion Act

            #region Assert
            Assert.AreEqual(50, unit.FullName.Length);
            Assert.IsFalse(unit.IsTransient());
            Assert.IsTrue(unit.IsValid());
            #endregion Assert
        }

        #endregion Valid Tests
        #endregion FullName Tests

 We can see "Act" region is always the same, and that the "Arrange" region is very similar except where we set the specific piece of data that we want to test, the name of the test always contains the field "FullName" that we are testing, but the "Assert" region is different because of what we are testing.

Steps to create the template

  1. Identify a set of tests with common structure that will probably be used again. As tests are added and you keep adding these similar tests over and over this will become obvious.
  2. Refactor the tests as much as possible to make them as generic as possible. This would include creating a generic method like "GetValid" to create a record that is always valid so we know that the test is only testing one specific element. BTW, this is a good practice anyway as it make it easier to fix many tests is something is changed on purpose (maybe a field is added or the validation is changed for an existing field).
  3. Now, copy and paste the tests you want to create into a new ReSharper live template.
    ReSharper Live Template menu -> ReSharper Create Menu -> ReSharper Paste code into template
  4. Uncheck reformat.
  5. Identify values that will change depending on the field name and record that the tests will be run against. For this example, that would be unit and FullName. Replace them with ReSharper values line $FieldToTest$ and $Entity$.
  6. Everywhere these names occur, replace them With the ReSharper variable. This can be in comments, method names, or part of variable names.
  7. The two other ReSharper variables used in my example are $entityVariable$ and $MaxLength$
  8. ReSharper Variable Macros$Entity$ should be set to not to use a macro and the first time this appears is in the code. You want to set the Editable Occurrence to be a place in the code where intellisense will let the Entity be populated without typing the full name.
  9. The $FieldToTest$ is also set not to use a macro, but the first occurrence in the code is in position #4. Note, for intellisense to work correctly, the order of the macros can be important. In my example, the $Entity$ is first.
  10. $entityVariable$ is not editable because this is set with the macro to be the name of the $Entity$ but starting with a lower case letter.
  11. $MaxLength$ is used in the tests checking the maximum length of the string. 1 is added to it to make it fail the validation.
  12. Once you have created this, test the template with different records and different fields (with the same type of validation) to see if you missed anything.

The ReSharper template

        #region $FieldToTest$ Tests
        #region Invalid Tests

        /// <summary>
        /// Tests the $FieldToTest$ with null value does not save.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ApplicationException))]
        public void Test$FieldToTest$WithNullValueDoesNotSave()
        {
            $Entity$ $entityVariable$ = null;
            try
            {
                #region Arrange
                $entityVariable$ = GetValid(9);
                $entityVariable$.$FieldToTest$ = null;
                #endregion Arrange

                #region Act
                $Entity$Repository.DbContext.BeginTransaction();
                $Entity$Repository.EnsurePersistent($entityVariable$);
                $Entity$Repository.DbContext.CommitTransaction();
                #endregion Act
            }
            catch (Exception)
            {
                Assert.IsNotNull($entityVariable$);
                var results = $entityVariable$.ValidationResults().AsMessageList();
                results.AssertErrorsAre("$FieldToTest$: may not be null or empty");
                Assert.IsTrue($entityVariable$.IsTransient());
                Assert.IsFalse($entityVariable$.IsValid());
                throw;
            }
        }

        /// <summary>
        /// Tests the $FieldToTest$ with empty string does not save.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ApplicationException))]
        public void Test$FieldToTest$WithEmptyStringDoesNotSave()
        {
            $Entity$ $entityVariable$ = null;
            try
            {
                #region Arrange
                $entityVariable$ = GetValid(9);
                $entityVariable$.$FieldToTest$ = string.Empty;
                #endregion Arrange

                #region Act
                $Entity$Repository.DbContext.BeginTransaction();
                $Entity$Repository.EnsurePersistent($entityVariable$);
                $Entity$Repository.DbContext.CommitTransaction();
                #endregion Act
            }
            catch (Exception)
            {
                Assert.IsNotNull($entityVariable$);
                var results = $entityVariable$.ValidationResults().AsMessageList();
                results.AssertErrorsAre("$FieldToTest$: may not be null or empty");
                Assert.IsTrue($entityVariable$.IsTransient());
                Assert.IsFalse($entityVariable$.IsValid());
                throw;
            }
        }

        /// <summary>
        /// Tests the $FieldToTest$ with spaces only does not save.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ApplicationException))]
        public void Test$FieldToTest$WithSpacesOnlyDoesNotSave()
        {
            $Entity$ $entityVariable$ = null;
            try
            {
                #region Arrange
                $entityVariable$ = GetValid(9);
                $entityVariable$.$FieldToTest$ = " ";
                #endregion Arrange

                #region Act
                $Entity$Repository.DbContext.BeginTransaction();
                $Entity$Repository.EnsurePersistent($entityVariable$);
                $Entity$Repository.DbContext.CommitTransaction();
                #endregion Act
            }
            catch (Exception)
            {
                Assert.IsNotNull($entityVariable$);
                var results = $entityVariable$.ValidationResults().AsMessageList();
                results.AssertErrorsAre("$FieldToTest$: may not be null or empty");
                Assert.IsTrue($entityVariable$.IsTransient());
                Assert.IsFalse($entityVariable$.IsValid());
                throw;
            }
        }

        /// <summary>
        /// Tests the $FieldToTest$ with too long value does not save.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ApplicationException))]
        public void Test$FieldToTest$WithTooLongValueDoesNotSave()
        {
            $Entity$ $entityVariable$ = null;
            try
            {
                #region Arrange
                $entityVariable$ = GetValid(9);
                $entityVariable$.$FieldToTest$ = "x".RepeatTimes(($MaxLength$ + 1));
                #endregion Arrange

                #region Act
                $Entity$Repository.DbContext.BeginTransaction();
                $Entity$Repository.EnsurePersistent($entityVariable$);
                $Entity$Repository.DbContext.CommitTransaction();
                #endregion Act
            }
            catch (Exception)
            {
                Assert.IsNotNull($entityVariable$);
                Assert.AreEqual($MaxLength$ + 1, $entityVariable$.$FieldToTest$.Length);
                var results = $entityVariable$.ValidationResults().AsMessageList();
                results.AssertErrorsAre("$FieldToTest$: length must be between 0 and $MaxLength$");
                Assert.IsTrue($entityVariable$.IsTransient());
                Assert.IsFalse($entityVariable$.IsValid());
                throw;
            }
        }
        #endregion Invalid Tests

        #region Valid Tests

        /// <summary>
        /// Tests the $FieldToTest$ with one character saves.
        /// </summary>
        [TestMethod]
        public void Test$FieldToTest$WithOneCharacterSaves()
        {
            #region Arrange
            var $entityVariable$ = GetValid(9);
            $entityVariable$.$FieldToTest$ = "x";
            #endregion Arrange

            #region Act
            $Entity$Repository.DbContext.BeginTransaction();
            $Entity$Repository.EnsurePersistent($entityVariable$);
            $Entity$Repository.DbContext.CommitTransaction();
            #endregion Act

            #region Assert
            Assert.IsFalse($entityVariable$.IsTransient());
            Assert.IsTrue($entityVariable$.IsValid());
            #endregion Assert
        }

        /// <summary>
        /// Tests the $FieldToTest$ with long value saves.
        /// </summary>
        [TestMethod]
        public void Test$FieldToTest$WithLongValueSaves()
        {
            #region Arrange
            var $entityVariable$ = GetValid(9);
            $entityVariable$.$FieldToTest$ = "x".RepeatTimes($MaxLength$);
            #endregion Arrange

            #region Act
            $Entity$Repository.DbContext.BeginTransaction();
            $Entity$Repository.EnsurePersistent($entityVariable$);
            $Entity$Repository.DbContext.CommitTransaction();
            #endregion Act

            #region Assert
            Assert.AreEqual($MaxLength$, $entityVariable$.$FieldToTest$.Length);
            Assert.IsFalse($entityVariable$.IsTransient());
            Assert.IsTrue($entityVariable$.IsValid());
            #endregion Assert
        }

This ReSharper Template can be downloaded here to be imported into your version. Rename the ShortCut and description to something meaningful to you.

Add common string tests