4.1. Integration Tests versus Unit Tests

As integration tests cross boundaries they are more fragile than unit tests. This is one of the reasons why you should always store your integration tests in a separate assembly from your unit tests. The aim for your unit tests is to run consistently and to always be green. If your unit tests are not green then you have broken something. Integration tests are different. They might fail for other reasons outside of your control. For example, if the database is not in the expected state or even accessible, then the test will fail. This is why they are fragile and why you want to separate the two types — so that you can keep the warm feeling of seeing the green bar for as long as possible. By dividing, you have more chance of always seeing a totally green bar. This is important, and it comes back to the broken window syndrome mentioned in Chapter 2. If you always see some tests are broken, then you'll become used to it and let things slide. If you have your unit and integration tests together, you'll be used to seeing red appear every now and again. As a result, you won't notice when unit tests have failed. As discussed in the previous chapter, the name of your project would be ProjectName.Tests.Integration.

The other reason is for speed, because integration tests are generally slower than unit tests. You'll generally be running your unit tests more often because they contain all your logic, so you'll want these tests to run much faster. To give you an indication of this, a more complete version of the application has 93 unit tests. To execute them, it took 2.49 seconds:

93 passed, 0 failed, 0 skipped, took 2.49 seconds (NUnit 2.5).

In terms of integration tests, executing 26 tests took 27.46 seconds:

26 passed, 0 failed, 1 skipped, took 27.46 seconds (NUnit 2.5).

As you can see, the time difference soon adds up when you start crossing boundaries and external systems.

Now you might be thinking that integration tests are pointless if they are this slow and fragile. But it isn't as bad as it sounds. Remember, this is only compared to unit tests; if you architect your tests in the correct fashion — as will be explained later in this chapter — then you will be able to speed up your tests and take advantage of them failing less often.

Finally, after you have the tests in place you'll need to execute them. Ideally, you should be running the tests as often as possible. However, if this is not possible then you should at least run the tests after making a change — which is covered by the tests themselves — to verify that you haven't broken anything. When it comes to the build, your integration tests should be executed as a separate build after your main build has completed successfully. This means that you build and run your unit tests as normal; if this is successful, you start a second build which runs your integration tests. As before, you'll want to separate tests which might fail for different reasons. You don't want to fail the main build because your database server was restarted.

Although this defines integration tests, what they actually cover varies from application to application. In terms of your example application WroxPizza, the integration tests will cover three main sections:

  • NHibernate mapping between domain objects and tables

  • Repositories implementation

  • ASP.NET security and caching

Alongside this, it would also be common to see integration tests focusing on the following:

  • Email communication

  • Web services

These all share the common traits of being potentially fragile compared to your nicely isolated unit tests. However, the tests around these sections of the application are important as you still need to verify that they work as expected. During the following sections, you will take the same features as discussed in Chapter 3 but explain them with regard to the integration tests and how you can verify the additional functionality required by the application.

4.1.1. Feature 1: Displaying Products from Database

In your first feature, you created a domain mapping for your Product object. This enabled NHibernate, likewise with other ORM frameworks, to understand how to communicate between your object and the underlying table. With Fluent NHibernate, you can verify that these mappings are correct. You'll use a PersistenceSpecification object that can verify that your object, mapping, and schema are configured correctly. The following example demonstrates how to create a test which will verify the mapping for your Product entity model:

[TestFixture]
public class ProductMapTests : NHibernateFixtureBase

{
        [Test]
        public void Product_Mappings_Are_Set_Correctly()
        {
            new PersistenceSpecification<Product>(Session)
                .CheckProperty(x => x.Name, "Cheese Pizza")
                .CheckProperty(x => x.Description, "A Cheese Pizza")
                .CheckProperty(x => x.PizzaTopping, true)
                .CheckReference(x => x.Category, new ProductCategory {ID = 1,
Description = "Pizza"})
                .CheckProperty(x => x.BasePrice, 13.23)
                .VerifyTheMappings();
        }
    }

This is another useful verification to ensure that your mapping behaves as you might expect. This becomes more useful when you have relationships to other domain objects. In the previous example you have a Category reference which should accept a Category object that already exists in the database. This test will verify that the relationship between the two objects has been set up correctly, which we found to be very useful during development.

For these mappings to verify, they need a Session object. A Session is an active connection to a database which NHibernate uses. To provide this, you should create a NHibernateFixtureBase. This base fixture will create your Session which can be used by all your mapping tests. The most important part of the fixture is the Setup method. Within the Setup method, you create your session to the database as defined in our App.Config config. You can then take advantage of your NHibernate mapping to build the schema. NHibernate has enough information about your database to build a complete schema. This allows you to rebuild your schema for every test — this means you're confident about the expect state of your database. You don't have to worry that it will be attached to an older version of your database. Below is the NHibernateFixtureBase which the tests inherit from:

public class NHibernateFixtureBase
{
    protected SessionSource SessionSource { get; set; }
    protected ISession Session { get; private set; }

    [SetUp]
    public void Setup()
    {
        SessionSource = new SessionSource(new NHibernateModel());
        Session = SessionSource.CreateSession();
        SessionSource.BuildSchema(Session);
        CreateInitialData(Session);
        Session.Flush();
        Session.Clear();
    }
}

The other object you'll be working with is the NHibernateModel, which is part of your main assembly as it is used in DefaultNHibernateConfig created earlier. This model simply defines how to find the assembly containing your fluent NHibernate mapping objects.

public class NHibernateModel : PersistenceModel
{

public NHibernateModel()
        {
                 addMappingsFromAssembly(typeof(ProductMap).Assembly);
        }
}

The final part of the puzzle is the app.config configuration for your database connection. This is automatically picked up by NHibernate and looks similar to the following example but looks different than the configuration used in Chapter 3 as you are no longer connected to a SQL Server instance. Instead, to improve the performance and reliability of your tests, you can use SQLite. SQLite is a database implementation; however, it has the advantage of being able to store the database in memory. This means that the database is extremely quick as well as being easy to create and destroy:

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
   <session-factory>
      <property name="connection.provider">
         NHibernate.Connection.DriverConnectionProvider
      </property>
      <property name="dialect">
         NHibernate.Dialect.SQLiteDialect
      </property>
      <property name="connection.driver_class">
         NHibernate.Driver.SQLite20Driver
      </property>
      <property name="connection.connection_string">
         Data Source=:memory:;Version=3;New=True;
      </property>
      <property name="connection.release_mode">
         on_close
      </property>
      <property name="show_sql">
         true
      </property>
   </session-factory>
</hibernate-configuration>

By taking advantage of NHibernate's provider model, you can point your connection to either SQL Server or SQLite without affecting the previous layers. This is amazing for testing purposes as you can use the correct database type at the correct point in time.

This means that you can test your mappings against a SQLite database in a very quick fashion without having to worry about setting up servers, instances, databases, and so on — NHibernate can do all this for you.

The foundation you have laid down can be reused and taken advantage of when you test your Repositories. The Repositories will also take advantage of using the in-memory SQLite instance for the same reasons.

In the next step you will implement the ProductRepository. As discussed in Chapter 3, this has the responsibility of returning different products from the database to the controller. The most important logic happens within the setup stage.

The Setup method constructs the ProductRepository object, calling a helper method to do similar setups to the previous Setup method. After you have the Repository, you can construct two test objects, which you will use as known data. Having known data in the database is a must as you need to use this to verify the results. If your tests do not define the database content, then your tests will become fragile. They generally become fragile because of outside influences — for example, other tests, or developers, changing the data:

[SetUp]
public void Setup()
{
    repository = new ProductRepository(Helper.GetTestDatabase());

    Product p = new Product { Name = "FirstTestProduct",
                        Description = "FirstTestDesc",
                        BasePrice = 123 };

    Product p2 = new Product { Name = "SecondTestProduct",
                        Description = "SecondTestDesc",
                        BasePrice = 123 };

    repository.SaveOrUpdate(p);
    repository.SaveOrUpdate(p2);
}

However, you need a way to insert data into your Repository. You need a way to insert your two products into the Repository so that you can retrieve them. The most appropriate solution would be to use a different mechanism to insert the data such as using ADO.NET directly. The reason for using a different mechanism is that you remove the dependency on the Repository implementation, plus you can be more confident that it works as expected as you set up your known state via a different route. However, setting up a known state is complex and made even more difficult with SQLite in-memory databases. Due to this, the example uses the Repository to insert your known data. As discussed in the next section, this logic is shared across multiple Repositories — this reduces your risk, and if it breaks, then you can be confident you will be alerted quickly.

Yet, as you are using the Repository in your test code, you still need to test the implementation. You now have a tight coupling. You need to insert data to verify "Get" works; however, you need to retrieve data to verify insert works. Given this situation, you need to somehow break the decoupling. In this situation, I like to find a way to verify that one works without being dependent on the other method. Our ProductRepostiory will inherit a base class called NHibernateRepository. This will be a generic class to allow you to share the common functionality across mulitple different implementations.

When we originally started to code the NHibernateRepository, we were testing the object via the ProductRepository as if the two were a single unit. This was wrong. The two are separate; the fact that they share functionality doesn't matter. The base class needs to be tested and verified in its own right. With this in mind, we created a NHibernateRepositoryTests class. This class follows the same pattern of the ProductRepository where you have your Setup method to define your Repository and a sample. You still need to provide a concrete type to test against, however this is irrelevant to the actual tests:

[SetUp]
public void Setup()
{
   repository = new NHibernateRepository<Product>(Helper.GetTestDatabase());
   p = new Product {ID = 0, Name = "TestName", Description = "TestDesc"};
}

Your NHibernate Repository can now be tested. As mentioned, you need a way to insert the SaveOrUpdate method without having to "Get" the item, as this hasn't been implemented yet.

When NHibernate inserts an item into the database, if it has an auto increment ID, as the Product object does, then NHibernate will increase the value of the ID. If the insert worked as you expected, then the ID would not be 0 as you initialized the variable to. This is how you test to verify that the insert worked without having to retrieve the item:

[Test]
public void SaveOrUpdate_Inserts_New_Product_Into_Database()
{
   Product update = repository.SaveOrUpdate(p);
   Assert.AreNotEqual(0, update.ID);
}

When you are confident that the SaveOrUpdate() method works as expected, you can implement your GetAll method and use the previous SaveOrUpdate method to help verify it worked as expected. The test method in your NHibernateRepositoryTests will first insert your temporary Product, and then call GetAll and verify that the count is 1:

[Test]
public void GetAll_Returns_All_Known_Items_In_Repository()
{
   repository.SaveOrUpdate(p);
   List<Product> products = repository.GetAll();

   Assert.AreEqual(1, products.Count);
}

The implementation to ensure these tests pass looks like this:

public List<T> GetAll()
{
    ICriteria criteria = Session.CreateCriteria(typeof(T));
    return criteria.List<T>() as List<T>;
}

public T SaveOrUpdate(T entity)
{
    using(Session.BeginTransaction())
    {
          Session.SaveOrUpdate(entity);
          Session.Transaction.Commit();
          return entity;
    }
}

With these two methods in place, you have your core functionality for your first feature implemented and tested. Because your ProductRepository inherits from NHibnerateRepository, your Repository has inherited the GetAll and SaveOrUpdate functionality. Your class definition is similar to the following:

class ProductRepository : NHibernateRepository<Product>, IProductRepository

In both your Setup methods, you take advantage of a GetTestDatabase() method. This constructs the DefaultNHibernateConfig object that is responsible for configuring NHibernate, adding mapping files, and creating a session factory. To create your schema for testing, the object has been extended to have a custom method called GenerateSchemaCreationScript:

public static IDbConfigure GetTestDatabase()
{
    DefaultNHibernateConfig db = new DefaultNHibernateConfig();
    string[] script = db.GenerateSchemaCreationScript();
    executeScripts(script, db.GetCurrentSession().Connection);

    return db;
}

This GenerateSchemaCreationScript is what will produce your table and create scripts, which you can execute against your database to produce your schema:

public string[] GenerateSchemaCreationScript()
{
    return cfg.GenerateSchemaCreationScript(GetSessionFactory().Dialect);
}

After your GetTestDatabase method has been executed, you have a new database with an up-to-date schema together with a SessionFactory waiting for your tests to start executing.

NHibernate provides you with a number of nice features which you can take advantage of during testing. However, if you are using another method of data access then you might not be able to take advantage of this. In this case, what should you do?

First, it depends on how you develop your data access. No matter which framework you use to develop your layer, there are some concepts which you must follow:

  • Ensure you are testing against the latest database schema. If you are not using NHibernate, then you will manually create the schema. The best advice is to keep the creation script in a file or set of files. For every test, or at least every Test fixture, you should create the database from these creation scripts. You can then use these same creation scripts to deploy to your production database. There are tools available to help manage this stage. Microsoft offers Visual Studio for Database Professionals (Data Dude), while Red Gate Software has SQL Compare. Both products provide support for managing your database schema.

  • Ensure the tests are responsible for generating test data. Similar to your database schema, it's important to test against a known data set. Again, Data Dude can help while Red Gate has a tool for SQL Data Generator for producing large amounts of test data. The other approach is to insert the data within the Setup block in a similar fashion to those shown previously. The final approach is to use Test Data Builders, as discussed in Chapter 5.

How you achieve this depends on your implementation, but as long as you keep this in mind you should be able to successfully test against the database.

In summary, in order to test this feature that interacts with the database we had to create a number of different helper methods and base classes to support creating the database and forming a connect. By taking advantage of NHibernate we could use the framework to create our database based on our mapping files created in Chapter 3 and use SQLite to store the database in memory.

4.1.2. Feature 2: Adding Categories

After you have created your NHibernateRepository you can start extending your ProductRepository to meet more of your requirements.

The second feature implemented was the ability to return categories for the products. As you are now implementing features on the ProductRepository, your tests should live within the ProductRepositoryTests class.

The first test is to ensure that the category is returned for each product in your Repository — you've already inserted your two products in the Setup method. After you return the products from the database, loop around and verify that they have a Category object and a valid description:

[Test]
public void When_Product_Returned_The_Category_Object_Is_Also_Returned()
{
  List<Product> products = repository.GetAll();
  foreach (var product in products)
  {
    Assert.IsNotNull(product.Category);
    Assert.IsNotNullOrEmpty(product.Category.Description);
  }
}

After this passes you can be confident that the relationship is set up correctly. The rest of the logic is handled by the controller to determine which products to display.

The next feature is for returning products within a certain category. For this feature, you don't want to return all the products and then display only certain products. Instead, you only want certain products to be returned from the database for a particular category, and as such improve performance.

The test is as you would expect by now. The method is called GetProductsByCategory, which returns a collection. Within the Setup method, you inserted two products with different categories. As such, when you request the products for one of those particular categories you only expect one to be returned:

[Test]
public void GetProductsByCategory_Returns_Only_Products_In_Category()
{
  List<Product> products = repository
                 .GetProductsByCategory("FirstTestCategory");

  Assert.AreEqual(1, products.Count);
}

In terms of what you pass to the controller, if no products are found, then you expect a list to be returned with a product count of 0 instead of null. To define this behavior you need a test. This will ensure that if this behavior changes, it will be caught:

[Test]
public void ProductRepository_No_Products_Returns_Empty_List()
{

List<Product> products = repository
                  .GetProductsByCategory("NonExistentCategory");

   Assert.AreEqual(0, products.Count);
}

With the behavior defined within the tests, you can implement the query against your NHibernate layer to pull back the required results as a List<Product> collection:

public virtual List<Product> GetProductsByCategory(string category)
{
    ICriteria criteria = Session.CreateCriteria(typeof (Product))
                                .CreateAlias("Category", "c")
                                .Add(Expression.Eq("c.Description",
                                                   category))
                                .SetResultTransformer(resultTransformer());
    return criteria.List<Product>() as List<Product>;
}

With this method in place, the control will be able to use and interact with the Repository and return the required information from the database.

4.1.3. Feature 3: Adding Items to the Shopping Cart

If you remember back to Chapter 3, when adding an item to the shopping cart you either get an existing shopping cart for the user or if null was returned then you should create a new cart.

For the first part of your Repository you need to create the ability to retrieve a cart for a particular user. To add a cart, you simply need to insert an instance of a ShoppingCart object. As this is part of the shared functionality it has already been tested and implemented.

The second stage is a custom method on the IShoppingCartRepository which is similar to how the GetProductsForCategory method worked — where you query the database and select a single object.

After the test inserts a cart you need to retrieve the cart before verifying that the two are equal:

[Test]
public void GetCart_Returns_Cart_Based_On_Username()
{
  repository.SaveOrUpdate(cart);

   ShoppingCart cart1 = repository.GetCart(username);
   Assert.AreEqual(cart, cart1);
}

To match the second part of the implementation, if the cart cannot be found, then you need to return null. Again, you need to define the expected behavior as a test to ensure that it doesn't change without you being aware, which could potentially cause other parts of the system to break:

[Test]
public void If_cart_does_not_exist_then_getcart_should_return_null()

{
    ShoppingCart cart1 = repository.GetCart(string.Empty);
    Assert.IsNull(cart1);
}

The implementation to match this method is shown in the following:

public ShoppingCart GetCart(string username)
{
    ICriteria criteria = Session.CreateCriteria(typeof (ShoppingCart))
                                .Add(Expression.Eq("Username", username));

    return criteria.UniqueResult<ShoppingCart>();
}

To verify that the implementation of NHibernate and your mappings are working correctly, you want to verify that a product is also returned as an item in your cart when the cart is retrieved. Although you aren't testing any code directly, you are verifying that your system is working as expected and defining the behavior. This is an important stage of development and testing. The test is shown here:

[Test]
public void GetCart_Returns_Products_Within_Cart()
{
    cart.AddItem(p);
    repository.SaveOrUpdate(cart);

    ShoppingCart cart1 = repository.GetCart(username);
    Assert.AreEqual(1, cart1.Items.Count);
    Assert.AreEqual("productName", cart1.Items[0].Product.Name);
}

In terms of the feature, this is your implementation complete. The other functionality is inherited.

The next major problem you face doesn't have to do with your Repositories, instead it relates to your ASP.NET membership security provider. To obtain the shopping cart, you need to access the user's credentials. You do this by asking the ASP.NET built-in membership provider. This handles all the authentication and credentials for you. However, because the ASP.NET pipeline stores this information, it makes it very difficult to test.

In terms of your systems implementation, you already have an ICredentialsService which will return a username for the currently logged-in user. The ASPnetCredentialsService object implements ICredentialsService to provide this functionality:

[Test]
public void GetUsername_Returns_Current_Logged_In_User()
{
  ISecurityContext context = MockRepository.GenerateStub<ISecurityContext>();
  context.Stub(c => c.GetUser()).Return(new StubMembershipUser("TestUser",
                                                               Guid.Empty));
  ASPnetCredentialsService service = new ASPnetCredentialsService(context);
  Assert.AreEqual("TestUser", service.GetUsername());
}

As you can see from the test for ASPnetCredentialsService, you need an ISecurityContext which has a GetUser() method. This is where you hit your first major problem. The GetUser method needs to return a MembershipUser object which is part of ASP.NET. However, you are unable to construct this object in your tests as it has a private constructor which means you also can't mock CredentialsService. You also need to create a manual StubMembershipUser. This inherits from MembershipUser to allow you to use it; however, you have to override the username property so you can set the value in your constructor:

public class StubMembershipUser : MembershipUser
{
   public StubMembershipUser(string username, Guid userKey)
   {
      _username = username;
      _userKey = userKey;
   }

   private string _username;
   public override string UserName
   {
      get { return _username; }
   }
}

The result is that you can verify that the ASPnetCredentialsService correctly talks to the ISecurityContext object and can obtain the data from a MembershipUser object.

After you have done this, you need to actually obtain a live instance of your MembershipUser based on the user currently logged in. As your tests don't run in the ASP.NET lifecycle, you don't have information available. During your tests, you need to mock this information; in the real system you need to access ASP.NET. This can be done in a similar way to your SystemTime and DateTime where during test execution you can override the current user.

To verify that the HttpSecurityContext, which implements ISecurityContext, is working as you expect you want to verify that the object correctly calls the function, accesses the ASP.NET Membership Provider as expected, and returns the correct MembershipUser object.

To make this happen, your test needs to replicate the information ASP.NET provides. ASP.NET provides an Identity object. One of the properties on this object is called Name, which contains the username to use. Within your test, you create a stub of this object. You then reassign a function on the object of CurrentUser to use your stub Identity object instead of ASP.NET. After calling GetUser, you verify that the user was correct:

[Test]
public void GetUser_Calls_HttpContext()
{
      HttpSecurityContext httpSecurityContext = new HttpSecurityContext(null);
      //Mock current user so it attempts to get access to a username of our
      //choice in our tests but actual implementation will go via ASP.net
      var stub = MockRepository.GenerateStub<IIdentity>();
      stub.Stub(i => i.Name).Return(username);

      httpSecurityContext.CurrentUser = () => stub;

MembershipUser user = httpSecurityContext.GetUser();
      Assert.IsNotNull(user);
      Assert.AreEqual(username, user.UserName);
}

The test is not complex. The complex section is generally getting the system into a testable state. To make this testable, you abstract away as much as possible from the dependency on ASP.NET. This allows the tests to be more focused and flexible. If you had merged ICredentialsService and ISecurityContext into one, the overall complexity would have increased.

After you have abstracted, you created a CurrentUser hook function that allows you to change the implementation of the object under test, making it easier to test. Accessing this method is key to making the feature testable as you can remove the dependency on ASP.NET.

The implementation of the method isn't very complex either:

public Func<IIdentity> CurrentUser = () => Current.User.Identity;

An alternative solution to using a Func in C# 3.0 would have been to create a TestableHttpSecurityContext for use during the testing. This object would inherit from HttpSecurityContext but override the method for obtaining the CurrentUser, leaving GetUser untouched.

However, you do have a problem with how the Membership object behaves. The Membership system needs access to a real SQL Server instance with the correct schema and data setup to access the Membership information. To make it more complex, the Membership system doesn't use NHibernate for data access and instead uses ADO.NET. As discussed in the previous section, there are a few rules to follow when tests need to connect to databases. First, the test should create the schema. Secondly, the test should create the data to test against.

In this case, you should create an ASPnetDBHelper method which knows how to construct the database and populate it with the data required by ASP.NET — not your test data:

[SetUp]
public void Setup()
{
   ASPnetDBHelper.ReCreate();
   ASPnetDBHelper.ExecuteSchema();
   ASPnetDBHelper.ExeuteData();
}

You need to re-create the database itself. This is done by attempting to drop the database in case it already exists and then calling the create command:

public static void ReCreate()
{
   ExecuteDbCommand("DROP");
   ExecuteDbCommand("CREATE");
}

Within the ExecuteDbCommand method, you replace the database name in the connection string to master. This is because you don't want your initial catalog to be the database you're creating as it doesn't exist yet. You then use ADO.NET to execute the command:

static void ExecuteDbCommand(string start)
{
      string dbname = "WroxPizzaTestDB";
      string connToMaster = connString.Replace(dbname, "master");
      SqlConnection sqlConnection = new SqlConnection(connToMaster);
      SqlCommand command = new SqlCommand(start + " DATABASE " + dbname,
                                          sqlConnection);

      try
      {
         sqlConnection.Open();
         command.ExecuteNonQuery();
      }
      catch (Exception)
      { }
      finally
      {
         if (sqlConnection.State == ConnectionState.Open)
            sqlConnection.Close();
      }
}

In terms of generating your schema and data, SQL Compare and SQL Data Compare from Red Gate are used. This allows you to script schema creation scripts and data insertion commands into two single .sql files.

Your DB Helper will simply read in the file and call Execute:

public static void ExecuteSchema()
{
   string path = Path.Combine(location, @"Schema.sql");
   string schema = new StreamReader(path).ReadToEnd();
   Execute(schema);
}
public static void ExecuteData()
{
   string path = Path.Combine(location, @"Data.sql");
   string data = new StreamReader(path).ReadToEnd();
   Execute(data);
}

Because SQL Compare and Data Compare produce scripts with GO statements, it means you cannot use ADO.NET to execute them. A GO statement indicates to the execution process that it is the end of a batch. ADO.NET does not understand this and will fail if it sees a GO statement. However, SQL Server Management Objects (SMO) can understand a SQL Script with GO statements. As such, you can use SMO objects to execute the SQL generated by the Red Gate tools:

private static void Execute(string script)
 {
    SqlConnection sqlConnection = new SqlConnection(connString);

Server server = new Server(new ServerConnection(sqlConnection));

    try
    {
       server.ConnectionContext.ExecuteNonQuery(script);
    }
    finally
    {
       if (sqlConnection.State == ConnectionState.Open)
          sqlConnection.Close();
    }
}

So the tests can access the scripts, they are included as a linked item in the Visual Studio Solution. You can then access the path based on where the assembly is being executed from:

private static string codebase = Assembly.GetExecutingAssembly().CodeBase;
private static string cbReplace = codebase.Replace("file:///", "");
private static string location = Path.Combine(Path.GetDirectoryName(cbReplace),
                                                                @"DataSchema");

The connection string is stored in the app.config and accessed via the ConfigurationManager in your tests:

<connectionStrings>
  <clear />
  <add name="LocalSqlServer"
   connectionString= "Data Source=(local);
                   initial catalog=WroxPizzaTestDB;
                   Integrated Security=True;"
   providerName="System.Data.SqlClient"/>
</connectionStrings>

private static string connString = ConfigurationManager.ConnectionStrings
                                      ["LocalSqlServer"].ConnectionString;

After you have your database, you need test data. The easiest way is to use the Membership object to create a new user. In your Setup method, you simply have the following lines of code:

MembershipCreateStatus status;
Membership.CreateUser(username, "test||1236", "[email protected]", "Book Title",
                     "Testing ASP.net", true, out status);

After you have finished, make sure to clean up by deleting the user:

[TearDown]
public void Teardown()
{
   Membership.DeleteUser(username);
}

With this in place, your test will now connect to the Membership service and query the provider for a user with a username of your choice, which you have created. When it's found, it will return it back to the test for you to verify to ensure it is the correct user and that the HttpSecurityContext is correct. Put this together with ASPnetCredentialsService and your authentication system will work as required for obtaining the shopping cart.

From this feature, hopefully you can see the types of processes you need to perform when testing certain features. Sometimes you will be faced with problems which don't look testable; however, with certain considerations it is generally possible to test a feature or at least be confident it will work as expected.

4.1.4. Feature 4: Displaying Addresses

Feature 4 involves letting the user enter a new address, or selecting an existing address for delivery. This address would then be stored in a cache for later use in the checkout process. Caching is a very simple task in ASP.NET as the foundation is built into the framework. As such, you just need to use it as required. However, testing the cache is not as simple.

First you read an InMemoryCacheService which implements the ICacheService we described in the previous chapter. This has two methods, Add and Get. In your implementation, you simply call the appropriate methods on your ActiveCache object, which is provided as a parameter to your constructor:

public class InMemoryCacheService : ICacheService
{
  //Cache is sealed.
  private static Cache ActiveCache { get; set; }
  public void Add(string cacheID, object cache)
  {
    ActiveCache.Insert(cacheID, cache);
  }

  public object Get(string cacheID)
  {
    return ActiveCache.Get(cacheID);
  }
}

The main problem when testing this is getting access to the Cache object. The Cache object is generally accessed via the current HttpContext. However, as the HttpContext doesn't exist you can't use it. If you initialized a new instance of Cache, and attempt to add items you will get a null reference exception. However, you can access a constructed and isolated version of the Cache object via the HttpRuntime object. You can then use this object to provide the backing for your InMemoryCacheService, allowing you to test the implementation:

[Test]
public void Add_Calls_Add_On_HttpContext()
{
   Cache c = HttpRuntime.Cache;
   InMemoryCacheService service = new InMemoryCacheService(c);
   service.Add("test", 123);

   Assert.AreEqual(123, c.Get("test"));

}

[Test]
public void Get_can_return_object_from_cache()
{
   Cache c = HttpRuntime.Cache;
   c.Insert("test", 123);

   InMemoryCacheService service = new InMemoryCacheService(c);
   Assert.AreEqual(123, service.Get("test"));
}

The reason for abstracting your Cache object into a separate class is to enable you to add an interface, allowing you to stub out the implementation in higher layers meaning you don't have to worry about getting access to the object and having this hanging around in your tests. By breaking it out, you reduce the complexity of your tests while creating a more decoupled system.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.124.145