UC Davis College of Agricultural & Environmental Sciences
Personal tools

Jason Sylvestre

Feb 14, 2011

A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance

by Jason Sylvestre — last modified Feb 14, 2011 10:47 AM

This exception "A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance" happens when:

Ok, this exception "A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance" can happen with NHibernate if you have an entity with a list of other entities with the .Cascade.AllDeleteOrphan() mapping.

If you clear the list by assigning it a new List<entity>(), this exception will appear.

Instead clear the list by using the .Clear() method.

 

Dec 09, 2010

create scripts for source control

by Jason Sylvestre — last modified Dec 09, 2010 09:44 AM
Filed Under:

When creating SQL scripts for source control, date time stamps make it difficult to see exactly what changed between versions.

Steps to create scripts to to create your database

1) Right click your database and choose Tasks->Generate Scripts...

Generate Scripts Menu

2) On the Choose Objects step, don't choose "Script entire database and all database objects" as this contains roles and other things we don't want. Instead choose "Select specific database objects". typically Tables, Views, and stored procedures.

Choose Objects

3) Choose where to save the script. Typically the query window is picked so it can be copied and pasted into a script file for source control.

4) Click on the Advanced button.

Advanced Button

5) Typically most values are not changed. To prevent the date time stamp, set "Include Descriptive Headers" to False

No Headers Choice

6) Press OK on the Advanced Scripting Options screen.

7) Press Next >, Next>, Finish

 

Nov 17, 2010

Extend MVC LabelFor Method

by Jason Sylvestre — last modified Nov 17, 2010 09:47 AM

The LabelFor method provides strongly typed labels using lambda expressions. Unfortunately, the default MVC one doesn't provide a way to add extra attributes and if you don't want the text from the field variable you have to use the DisplayNames data annotations. The method here uses the variable name or data annotation DisplayName, but allows the Camel Case variable to be split into words, add a colon to the end, add extra text, and/or specify an id value for the label.

Note, the "Inflector.Titleize(labelText)" is a UCDArch method to convert Camel case to titleized (first letter capitalized of each word) strings.

Usages:

<%= Html.LabelFor(a => a.Ticket.MessageBody, DisplayOptions.HumanizeAndColon) %>
<%= Html.LabelFor(a => a.Ticket.MessageBody, DisplayOptions.HumanizeAndColon extraText: " (Email Body)") %>
<%= Html.LabelFor(a => a.Ticket.MessageBody, DisplayOptions.HumanizeAndColon, extraText: " (Email Body)", generatedId:true) %>
<%= Html.LabelFor(a => a.Ticket.MessageBody, DisplayOptions.HumanizeAndColon, extraText: " (Email Body)", id: "MyLabelId") %>

Code:

using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Web.Mvc;
using UCDArch.Core.Utils;
 
namespace HelpRequest.Controllers.Helpers
{
 
    public static class LabelExtensions
    {
        public static MvcHtmlString Label(this HtmlHelper html, string expression, string id = "", bool generatedId = false)
        {
            return LabelHelper(html,
                               ModelMetadata.FromStringExpression(expression, html.ViewData),
                               expression, DisplayOptions.None, "", id, generatedId);
        }
 
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
        public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, DisplayOptions displayOptions, string extraText = "", string id = "", bool generatedId = false)
        {
            return LabelHelper(html,
                               ModelMetadata.FromLambdaExpression(expression, html.ViewData),
                               ExpressionHelper.GetExpressionText(expression), displayOptions, extraText, id, generatedId);
        }
 
        //public static MvcHtmlString LabelForModel(this HtmlHelper html)
        //{
        //    return LabelHelper(html, html.ViewData.ModelMetadata, String.Empty);
        //}
 
        internal static MvcHtmlString LabelHelper(HtmlHelper html, ModelMetadata metadata, string htmlFieldName, DisplayOptions displayOptions, string extraText, string id, bool generatedId)
        {
            string labelText = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
            if (String.IsNullOrEmpty(labelText))
            {
                return MvcHtmlString.Empty;
            }
            var sb = new StringBuilder();
            if (displayOptions == DisplayOptions.Humanize || displayOptions == DisplayOptions.HumanizeAndColon)
            {
                sb.Append(Inflector.Titleize(labelText));
            }
            else
            {
                sb.Append(labelText);
            }
            sb.Append(extraText);
            if (displayOptions == DisplayOptions.HumanizeAndColon && labelText.Substring(labelText.Length - 1) != ":")
            {
                sb.Append(":");
            }
 
            var tag = new TagBuilder("label");
            if (!string.IsNullOrWhiteSpace(id))
            {
                tag.Attributes.Add("id", id);
            }
            else if (generatedId)
            {
                tag.Attributes.Add("id", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName) + "_Label");    
            }
            
            tag.Attributes.Add("for", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName));
            tag.SetInnerText(sb.ToString());
 
            return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
        }
 
        
    }
    public enum DisplayOptions
    {
        Humanize,
        HumanizeAndColon,
        None
    }
}

 

Sep 30, 2010

Convert VS 2008 deploy project to VS 2010

by Jason Sylvestre — last modified Sep 30, 2010 02:10 PM

Convert a Visual Studio 2008 deployment project to Visual Studio 2010 deployment project using new and enhanced features. Also a step by step explanation on how to actually deploy your project using the file system method.

Existing functionality that will be removed or changed:

We will be removing the project.Deploy "HelpRequest.Deploy":

Project To Remove

If we examine this project the two important sections for us are the Compilation and the Deployment. Compilation has the directory where the files to be deployed are written and Deployment has Web.config file section replacement:

Compilation Tab
Deployment Tab

From the image above we see that the connection strings and appSettings get replaced. In our project we have Prod and Test versions of each of these:

Old way we have project settings

These contain settings that will replace the sections in the Web.config

<connectionStrings>
  <add name="MainDB" connectionString="Data Source=..." providerName="System.Data.SqlClient"/>
  <add name="CATBERT" connectionString="Data Source=..." providerName="System.Data.SqlClient"/>
  <add name="RATBERT" connectionString="Data Source=..." providerName="System.Data.SqlClient"/>
</connectionStrings>

and this:

<appSettings>
  <add key="RecaptchaPrivateKey" value="..."/>
  <add key="RecaptchaPublicKey" value="..."/>
  <add key="HelpDeskEmail" value="test@ucdavis.edu"/>
  <add key="AppHelpDeskEmail" value="test@ucdavis.edu"/>
  <add key="WebHelpDeskEmail" value="test@ucdavis.edu"/>
  <add key="LDAPUser" value="..." />
  <add key="LDAPPassword" value="..." />
</appSettings>

We will not remove these extra config files yet because we will copy the information for the web config transformation files later on.

next steps:

1) Delete the Deploy project.

2) Right click on Web.config and choose  "Add Config Transforms"

Config Transforms Menu Choice

 

This will create transform files for each configuration under the Web.config

Transforms created

 

3) We don't need a transform for debug, so we will just delete that one.

4) For each deployment you will do, open of the related config file. My examples will all use the Test configurations.

5) For now, keep the comments to use as examples. Open the old ConnectionStringTest.config file and copy and paste them into the Web.Test.config file.

6) Keep the name value because this is what the locator will match against.

7) Keep the attributes you will change.

8) Copy the xtd:Transform and xtd:Locator from the commented example to the end of each connection string:

  <connectionStrings>
    <add name="MainDB" connectionString="Your Test Values"
         xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
    <add name="CATBERT" connectionString="Your Test Values"
         xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
    <add name="RATBERT" connectionString="Your Test Values"
         xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
  </connectionStrings>

9) Delete the old ConnectionStringTest.config file because you don't need it anymore.

10) Do the same for the AppSettingsTest.Config file. There are good examples of different ways you can do that here: http://weblogs.asp.net/srkirkland/archive/2009/10/13/common-web-config-transformations-with-visual-studio-2010.aspx

 

In my example below, I've only changed the app settings that will change with the deployment between the development version, Test, and Production. This requires the web.config to have the values you will need for deployment.

  <appSettings>
    <add key="HelpDeskEmail" value="test1@ucdavis.edu" xdt:Transform="Replace" xdt:Locator="Match(key)"/>
    <add key="AppHelpDeskEmail" value="test2@ucdavis.edu" xdt:Transform="Replace" xdt:Locator="Match(key)"/>
    <add key="WebHelpDeskEmail" value="test3@ucdavis.edu" xdt:Transform="Replace" xdt:Locator="Match(key)"/> 
  </appSettings>

11) Delete the AppSettingsTest.config file because it is no longer needed.

12) Do the same for other configurations you may have like Production, but of course using the related transform file under the Web.config

New deployment steps

1) For your main project,

double click on properties.

Properties

 

 2) For each configuration, make your changes. These will mostly be in the Package/Publish Web tab.

Package Publish Tab

 3) Choose your configuration

Configuration

 

4) Build your solution

5) Right click on your main project and choose "Build Deployment Package"

Build Deployment Menu

 

6) Wait until it is done. By showing all files in your project, you can open the transformed Web.config to ensure it was created as you expect

Transformed Web Config

 7) In the same area, you can look in Package to see that the zip files was created.

8) Right click on you main project and choose publish

Publish menu

 9) Enter a profile name

Publish Profile

10) choose the Publish Method (File System for me and the target location). If you choose File System, you can specify a local path so validate that everything is created as you expect. You may or may not want to delete all existing files prior to publishing.

11) Click save.

12) Click Publish

13) repeat for other configurations

14)If you want the publish setting to be saved to source safe, "show all files" for the project, find "projectName".Publish.xml and include it in the project.

 

Sep 07, 2010

Mock a call to SmartServiceLocator

by Jason Sylvestre — last modified Sep 07, 2010 10:26 AM
Filed Under:

Mock SmartServiceLocator<IRepository<Entity>>.GetService()

If there is a static call using SmartServiceLocator to get the repository, the repository can be mocked in the Unit Test by registering an additional service. The UCDArch Controller test base class has an override-able method to do this.

 protected override void RegisterAdditionalServices(IWindsorContainer container)
        {
            TermCodeRepository = MockRepository.GenerateStub<IRepository<TermCode>>();
            container.Kernel.AddComponentInstance<IRepository<TermCode>>(TermCodeRepository);
            base.RegisterAdditionalServices(container);
        }

The TermCodeRepository can then have the expected values assigned to it in the individual unit test like this:

            var termCodes = new List<TermCode>();
            termCodes.Add(CreateValidEntities.TermCode(1));
            termCodes[0].IsActive = true;
            termCodes[0].SetIdTo("1");
            TermCodeRepository.Expect(a => a.Queryable).Return(termCodes.AsQueryable()).Repeat.Any();

The code that calls this looks like this:

        public static TermCode GetCurrent()
        {
            var repository = SmartServiceLocator<IRepository<TermCode>>.GetService();
            var term = repository.Queryable.Where(a => a.IsActive).OrderByDescending(a => a.Id).FirstOrDefault();
            Check.Require(term != null, "Unable to find valid term.");

            return term;
        }

Sep 01, 2010

Force refresh of data from database with NHibernate

by Jason Sylvestre — last modified Sep 01, 2010 09:25 AM
Filed Under:

Avoid the black magic of NHibernate thinking that it does not need to re-get the data from the database when doing unit tests.

Database items that automatically get populated like lists of other database records linked with a bag in the mapping file may not be populated if the GetById doesn't think that data has been changed.

For example, if a ceremony has related registrations, and the ceremony is added to those registrations, then the IList will probably not be populated in the ceremony.

You may think, I'll just do a get from the database and it will refresh. But no, NHibernate may think that you have an existing copy of the ceremony in the database, so it will avoid the call to the database, and your data will not be refreshed. To get around this, you want to evict the ceremony entity before getting it, this will force NHibernate to make the call to the database and the list will be populated.

NHibernateSessionManager.Instance.GetSession().Evict(entity);

 

Aug 24, 2010

StaleStateException: Unexpected row count: 0, expected: 1

by Jason Sylvestre — last modified Aug 24, 2010 03:11 PM
Filed Under:

With Unit Tests, what may cause this, and what to do to fix it StaleStateException: Unexpected row count: 0, expected: 1

If the Key value is a string instead of an int, and the mapping file has "<generator class="assigned" />" then the key has to be specified before it is added. This means without a force save, nHibernate thinks it should be doing an update instead of an insert. So do an "Repository.EnsurePersistent(Entity, true);"

Similar to this, if the int key has been manually set to be other than zero, nHibernate thinks you should be doing an update instead of an insert. Preferabbly, don't set the key value and everything should be Ok.

Aug 18, 2010

Ensure a mocked method is called

by Jason Sylvestre — last modified Aug 18, 2010 11:22 AM
Filed Under:

When writing a unit test, ensure a mocked method in an interface is called with expected parameters.

When writing the mock to allow an interface method to pass in the method you are testing, you may initially think you could use .repeat.atLeastOnce():

_emailService.Expect(a => a.SendRegistrationConfirmation(Controller.Repository, registration)).Repeat.
                AtLeastOnce();

Unfortunately, if you were to test this and remove the line of code from the method you are testing it would pass. Instead once your method has passed, you can assert that the method was called by using code like this:

_emailService.AssertWasCalled(a => a.SendRegistrationConfirmation(Controller.Repository, registration));

May 03, 2010

invalid assignment left-hand side

by Jason Sylvestre — last modified May 03, 2010 08:26 AM
Filed Under:

One of the ways you can get this error and how to fix it.

I had a hidden input field:

hiddenField

I was trying to assign a value to it

$("#displayAmount") = total.toFixed(2);

With the assignment like this it failed. To assign it correctly, I had to use this:

$("#displayAmount").val(total.toFixed(2));

Apr 21, 2010

Partial Class to improve ReSharper speed

by Jason Sylvestre — last modified Apr 21, 2010 01:38 PM
Filed Under:

If a file is very large, ReSharper can be slow for some of its operations. To improve ReSharper responsiveness, create a partial class.

Especially with some unit tests, the length of a test class can get very large which can slow down ReSharper because it has to scan through the file. If you notice this happening create a partial class and break the files into whatever form seems logical to you.

 You will almost always have an existing class that you want to split into a partial class instead of designing a partial calss from scratch, so the steps below will be with that in mind.

I prefer to have all partial classes in a single folder so that they are logically grouped together.

  1. To prevent naming conflicts, rename your existing file temporally.
  2. Create a folder with the name of your old class.
  3. Move your file into the new folder.
  4. Rename it back to what it was. In the future, you may want to rename this to have Init or some other name that makes sense to you.
  5. Change the "class" to "partial class"
  6. Create a new class/file. I suggest naming them the same with Part01, Part02 at the end of the file name.
  7. replace the class name with the name of the partial class and include the partial key word.
  8. If the original class inherited anything, the subsequent partial class don't have them.
  9. Cut and paste code into the new partial classes.

if you have any private properties, they may need to be changed to protected. You will notice any issues like this when you compile.

Apr 19, 2010

ReSharper 5.0 Test Runner

by Jason Sylvestre — last modified Apr 19, 2010 12:38 PM
Filed Under:

ReSharper 5.0 has fixed and improved their test runner.

 Previously, if a large number of tests were run that used abstract base classes the runner would crash with a stack overflow error. This has been fixed with 5.0 build 1659

Also in this build Ignored and skipped tests now appear:

 Resharper Test Results

Rhino Mocks Extensions -- GetArgumentsForCallsMadeOn

by Jason Sylvestre — last modified Apr 19, 2010 06:25 AM

Using NHibernate and Rhino Moscks for testing, use this method to get the arguments used.

When creating a unit test, if a value is created inside the method you are testing, it is useful to be able to determine what was used.

The most common time you would use this is when a database record is saved. We can get those values by using the following code:

TransactionRepository.AssertWasCalled(a => a.EnsurePersistent(Arg<Transaction>.Is.Anything));
var args = (Transaction)TransactionRepository.GetArgumentsForCallsMadeOn(a => a.EnsurePersistent(Arg<Transaction>.Is.Anything))[0][0];

 

Then we can assert that the values in the saved record (Transaction) are what we expect:

Assert.IsNotNull(args);
Assert.AreEqual(60.00m, args.Amount);

Apr 15, 2010

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

 

Apr 13, 2010

Unit Testing

by Jason Sylvestre — last modified Apr 13, 2010 01:00 PM
Filed Under:

Tips and tricks for writing unit tests and what to avoid

The best units tests are those that will not break when they are not supposed to. To achieve this result we want tests that test as few things as possible per test and that have the minimum amount of setup data. Sometimes it is not possible to do this when the methods you are writing tests for are large and complex, but when this happens it is best to create a single method to setup the data for the tests, then call this each time but only change the one thing that is being tested.

Tests should cover valid and invalid data, and cover boundary data. Meaning the valid data should contain minimum and maximum valid values so that if something is change by accident in the method you are testing it will show up in your test.

To make my tests as clear as possible, I have started creating three regions so you can quickly navigate to the part of the test you need to investigate. These are Arrange, Act, and Assert. These are only guides as many assert actions can be directly added to the method call in the act region.