Working with Integration Tests

This is a sample project demonstrating integration tests that include the Data Access Layer (DAL). This is using .Net Entity Framework version 6 and Microsoft’s MSTEST. See other blog posts regarding integration testing and unit testing here:

http://solidfish.com/unit-testing-vs-integration-testing/

http://solidfish.com/unit-testing-with-dependency-injection/

 

This example uses database migrations as part of the tests. It drops and recreates the database before running each test case. A TestSetup.cs class is created for this database setup:


TestSetup.cs
public static class TestSetup
    {
        public static void CreateDatabase()
        {
            // Create database
            ExecuteSqlCommand(Master, $@"
                CREATE DATABASE [Widgets]
                ON (NAME = 'Widgets',
                FILENAME = '{Filename}')");

            // Run the database migration
            var migration = new MigrateDatabaseToLatestVersion<WidgetsContext, WidgetsConfiguration>();
            migration.InitializeDatabase(new WidgetsContext());
        }

        public static void DestroyDatabase()
        {
            var fileNames = ExecuteSqlQuery(Master, @"
                SELECT [physical_name] FROM [sys].[master_files]
                WHERE [database_id] = DB_ID('Widgets')",
                row => (string)row["physical_name"]);

            if (fileNames.Any())
            {
                ExecuteSqlCommand(Master, @"
                    ALTER DATABASE [Widgets] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
                    EXEC sp_detach_db 'Widgets'");

                fileNames.ForEach(File.Delete);
            }
        }
...

 

Using MSTEST’s initializer and cleanup attributes, the test class calls the TestSetup before and after running each test method.

    
UserServiceTest.cs
[TestClass]
    public class UserServiceTests
    {
        [TestInitialize]
        public void Start()
        {
            TestSetup.DestroyDatabase();
            TestSetup.CreateDatabase();
        }

        [TestCleanup]
        public void End()
        {
            TestSetup.DestroyDatabase();
        }

        [TestMethod]
        public void Create_ValidUser_Success()
        {
            // Arrange
            var configuration = new WidgetsMappingConfiguration();
            var context = new DataContext("Widgets", configuration);
            var repository = new Repository(context);
            var userService = new UserService(repository);
            var email = "mockuser@widgets.com";

            // Act
            User user = userService.CreateUser(email);
            context.SaveChanges();

            // Assert
            Assert.IsTrue(user.UserId != 0);
            Assert.IsTrue(user.Email == email);
        }

        [TestMethod]
        public void Create_InvalidUser_Fail()
        {
            // Arrange
            var configuration = new WidgetsMappingConfiguration();
            var context = new DataContext("Widgets", configuration);
            var repository = new Repository(context);
            var userService = new UserService(repository);
            var email = string.Empty;

            // Act
            User user = userService.CreateUser(email);
            context.SaveChanges();

            // Assert
            Assert.IsNull(user);
        }
...

 

The database migration scripts are setup with the following PM console commands:

PM> Add-Migration Migration
Scaffolding migration 'Migration'.
The Designer Code for this migration file includes a snapshot of your current Code First model. This snapshot is used to calculate the changes to your model when you scaffold the next migration. If you make additional changes to your model that you want to include in this migration, then you can re-scaffold it by running 'Add-Migration Migration' again.

Before updating our database, we need to make sure we’re using the correct connection string. By default, data migration looks in the startup project for a connection string. Since our startup project is empty, we need to specify where it can find that connection string. I have it defined in the app.config of the Widgets.Data project and can reference it using the System.Configuration.ConfigurationManager.This can be seen on the WidgetsContext class:

public WidgetsContext() : base(ConnectionString) { }

private static string ConnectionString
{
  get
  {
    return System.Configuration.ConfigurationManager.ConnectionStrings["Widgets"].ConnectionString;
  }
}

With the correct connection string configured we can run the database migration command.

PM> Update-Database -Verbose
Using StartUp project 'Widgets'.
Using NuGet project 'Widgets.Data'.
Specify the '-Verbose' flag to view the SQL statements being applied to the target database.
Target database is: 'Widgets' (DataSource: (LocalDB)\MSSQLLocalDb, Provider: System.Data.SqlClient, Origin: UserCode).
Applying explicit migrations: [201801042306331_Migration].
Applying explicit migration: 201801042306331_Migration.
CREATE TABLE [dbo].[Cart] (
 [CartId] [int] NOT NULL IDENTITY,
 [UserId] [int] NOT NULL,
 [CreatedAt] [datetime] NOT NULL,
 CONSTRAINT [PK_dbo.Cart] PRIMARY KEY ([CartId])
)
CREATE INDEX [IX_UserId] ON [dbo].[Cart]([UserId])
CREATE TABLE [dbo].[Widget] (
 [WidgetId] [int] NOT NULL IDENTITY,
 [Description] [nvarchar](100) NOT NULL,
 [Price] [decimal](18, 2) NOT NULL,
 [Cart_CartId] [int],
 CONSTRAINT [PK_dbo.Widget] PRIMARY KEY ([WidgetId])
)
CREATE INDEX [IX_Cart_CartId] ON [dbo].[Widget]([Cart_CartId])
CREATE TABLE [dbo].[User] (
 [UserId] [int] NOT NULL IDENTITY,
 [Email] [nvarchar](100) NOT NULL,
 CONSTRAINT [PK_dbo.User] PRIMARY KEY ([UserId])
)
CREATE UNIQUE INDEX [IX_U_Email] ON [dbo].[User]([Email])
ALTER TABLE [dbo].[Cart] ADD CONSTRAINT [FK_dbo.Cart_dbo.User_UserId] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([UserId]) ON DELETE CASCADE
ALTER TABLE [dbo].[Widget] ADD CONSTRAINT [FK_dbo.Widget_dbo.Cart_Cart_CartId] FOREIGN KEY ([Cart_CartId]) REFERENCES [dbo].[Cart] ([CartId])
CREATE TABLE [dbo].[__MigrationHistory] (
 [MigrationId] [nvarchar](150) NOT NULL,
 [ContextKey] [nvarchar](300) NOT NULL,
 [Model] [varbinary](max) NOT NULL,
 [ProductVersion] [nvarchar](32) NOT NULL,
 CONSTRAINT [PK_dbo.__MigrationHistory] PRIMARY KEY ([MigrationId], [ContextKey])
)
INSERT [dbo].[__MigrationHistory]([MigrationId], [ContextKey], [Model], [ProductVersion])
VALUES (N'201801042306331_Migration', N'Widgets.Data.Migrations.WidgetsConfiguration', 0x1F8B080000000...000 , N'6.2.0-61023')

Running Seed method.
PM>

More details here

https://msdn.microsoft.com/en-us/library/jj591621(v=vs.113).aspx

 

 

 

Unit Testing with Highway.Data Framework

Explored a new abstraction framework called Highway.Data which sits on top of an ORM (an abstraction framework that sits on top of an abstraction framework… 🙂 ). It is an open source project created by a group called hwyfwk. They have a few other tools to help with testing and scaffolding other common design patterns into a .Net project. Highway.Data supports Entity Framework 6 as well as other ORMs like nHibernate.

http://hwyfwk.com/projects/data/

Highway.Data simplifies the EF context model by creating a single abstracted Repository.

 

Git Repository

https://github.com/johnlee/Widgets.IntegrationTests

 

Reference Links

WebAPI 5 using XUNIT

https://www.strathweb.com/2012/06/asp-net-web-api-integration-testing-with-in-memory-hosting/

http://dotnetliberty.com/index.php/2015/12/17/asp-net-5-web-api-integration-testing/

http://xunit.github.io/docs/getting-started-desktop#run-tests-visualstudio

http://www.almguide.com/2015/06/running-xunit-tests-in-tfs-build-vnext/

 

WebAPI Integration Tests using NUNIT

https://stackoverflow.com/questions/29966982/integration-tests-for-webapi-and-transactions

https://stackoverflow.com/questions/13857180/how-should-i-set-up-my-integration-tests-to-use-a-test-database-with-entity-fram

 

Highway Framework

http://hwyfwk.com/projects/data/start.html

https://github.com/HighwayFramework/Highway.Data

 

Pluralsight Course

Integration Testing of Entity Framework Applications (uses Highway Framework with NUNIT)
Michael Perry
https://app.pluralsight.com/library/courses/entity-framework-applications-integration-testing