Introduction
I have yet to find good examples of realistic Test Driven Development (TDD) and how to write proper unit tests for complex business applications that give one enough confidence to stop doing manual tests. Generally the examples show how to test a Stack or a LinkedList, which is far simpler than testing a typical N-tier application, especially if you are using Entity Framework, Linq to SQL or some ORM in the data access layer and doing logging, validation, caching, error handling in the middle tier. There are many articles, blog posts, and video tutorials on how to write unit tests, which I believe are all very good starting points, but all of these examples show basic tests and are not good enough to replace a QA team. In this article I will present some realistic unit and integration test examples which should help in writing tests that give confidence and help move gradually towards TDD.
Background
I will show you tests done on my open source project Dropthings, which is a Web 2.0 AJAX portal built using jQuery, ASP.NET 3.5, Linq to SQL, Dependency Injection using Unity, caching using Microsoft Enterprise Library, Velocity and so on. Basically it contains all the hot techniques in one project. The project is a typical N-tier application with a web layer, a business layer and a data access layer. Writing unit tests, integration tests and load tests for this project was challenging, and therefore it is interesting to share so that you can see how Unit Testing and Integration Testing is implemented in a real project and illustrates how to gradually move toward Test Driven Development.
First, for those unfamiliar with unit testing, integration testing, test driven development, etc, the following points can serve as a quick introduction:
- Unit testing is a way to write code to test a small unit of code, say a class. You write test methods, which test a Class method (a unit). For example, you write code to ensure a
Stack
's Pop
method returns the last pushed item. In a unit test, you test only one class at a time. If the class you are testing is dependent on some other class, then that class is stubbed or mocked (dummy implementation). For example, if you are testing a class that uses File
, while testing that class, a DummyFile
is used to eliminate the need for a physical file system. A unit test should test the logic of the class being tested, nothing else. To make classes switch from real implementation to dummy/stub implementation, interfaces are used in place of the real class. So, your classes should use IFile
or IDatabase
instead of using File
and Database
directly. There are many articles, blog posts, and video tutorials on how to write unit tests. One principle to remember during unit testing is: when you are unit testing a class, you should have no dependency on database, file, registry, web services, etc. You should be able to test any class in complete "isolation" and your classes should be designed to support complete "isolation." - Integration testing tests a class while it is integrated with other classes. For example, assume you are testing
CustomerData
, which depends on Linq to SQL and a database connection. In integration testing, you test CustomerData
’s methods by calling them and ensuring it does its job properly engaging all the related classes. Integration test is more popular among developers because it is really testing the system as it should be. - Test Driven Development (TDD) is an extreme form of unit testing. The general principle is to first write the unit tests, then write the actual code. For example, first write the unit test which tests the
CustomerData
class with no real code in the CustomerData
class at first. The CustomerData
class may contain functions like InsertCustomer
, DeleteCustomer
, GetCustomer
, etc. which do nothing more than return some dummy Customer
objects to satisfy the unit test. Once the unit tests are all working with the dummy data, then you start writing the real code inside each of CustomerData
’s methods, where you make it access the database and do the real processing. After writing the real code, your unit tests should pass with no change to the test code. In other words, testing drives the development effort. TDD also requires that classes are designed in such a way that no class depends on any other class directly. All the dependencies are via interfaces. For example, CustomerData
does not use SqlConnection
directly, instead it uses ISqlConnection
. All the dependencies are provided to CustomerData
in its constructor.
Test Driven Development is a complex topic. It takes time to get your head around it because it requires specific design and development approaches so that the code becomes unit testable. It also requires discipline to write the tests first and then write the actual implementation later. The benefit of doing TDD is that all code already has an automated test available. Moreover, classes must by necessity be designed based on how they are used, since the test code is written before writing the class implementation code.
Testing using Behavior Driven Development
I have a preference as to how I write tests, because I find the traditional way of writing unit tests insufficient. In pure unit tests, one test method is written to test one and only one method of some class and there’s one Assert
method to test only one expectation at a time. For example, if you are testing s Stack
's Pop
method, a single unit test method should only test one expectation of the Pop
method. Pop
should return the last item pushed onto the stack, so a traditional unit test method might be written as:
[Test]
public void Pop_Should_Return_The_Last_Pushed_Item()
{
Stack stack = new Stack();
stack.Push(1);
Assert.Equal(1, stack.Pop());
}
However, testing a single method for a single expectation is cumbersome. You have to write too many test methods to test each method's overall behavior. Moreover in every test method you have to setup the class under test with proper context and then call the method you want to test just to verify one specific expectation. I find a Behavior Driven Development (BDD) style more useful than the traditional "one function and one assert per test method" style. I also use xUnit
because I think this is the most pragmatic unit available, compared to NUnit
and mbUnit
. In this article I will do unit testing using xUnit and Subspec, which is an extension of xUnit.
In Behavior Driven Development, components are tested against their expected "behavior." and the behavior is defined in this form:
Given some initial context (the givens),
When an event occurs,
Then ensure some outcome.
For example,
Given an empty Stack
When an item is pushed into the stack and Pop
is called
Then the last item pushed onto the stack is returned, the item is removed from the stack, and any additional calls to Pop
throw an exception.
Here you define the complete behavior of the Pop
method. The method that tests this behavior should test all the expected and related behaviors of Pop
.
In the following sections I will show how to unit test components against their behavior, then show how to do integration tests against such behavior. Finally, I will show how to move into Test Driven Development where the tests are written before the code.
Unit testing using BDD
In this first example, we will unit test the data access layer. The data access layer deals with object persistence using Linq to SQL and also does caching at the entity level. For example, when you want to load a user, it first checks the cache to see if the user is already cached, if not, it loads the user from database and then caches it.
Let's look at PageRepository
, which deals with Page
entity persistence. It has the common Create, Read, Update, and Destroy (CRUD) methods. Consider an example method GetPageById
that takes a PageId
and loads that Page
from the database.
public class PageRepository : IPageRepository
{
#region Fields
private readonly IDropthingsDataContext _database;
private readonly ICache _cacheResolver;
#endregion Fields
#region Constructors
public PageRepository(IDropthingsDataContext database, ICache cacheResolver)
{
this._database = database;
this._cacheResolver = cacheResolver;
}
#endregion Constructors
#region Methods
public Page GetPageById(int pageId)
{
string cacheKey = CacheSetup.CacheKeys.PageId(pageId);
object cachedPage = _cacheResolver.Get(cacheKey);
if (null == cachedPage)
{
var page = _database.GetSingle<Page, int>(
DropthingsDataContext.SubsystemEnum.Page,
pageId,
LinqQueries.CompiledQuery_GetPageById);
page.Detach();
_cacheResolver.Add(cacheKey, page);
return page;
}
else
{
return cachedPage as Page;
}
}
PageRepository
takes IDropthingsDataContext
, which is a unit testable with Linq to SQL DataContext
. By default, Linq to SQL will not generate a DataContext
that is unit testable. You will have to try Kazi's method to make a DataContext
unit testable. Next, it takes an ICache
which is an interface that deals with the cache. Assume somewhere there's an implementation of this interface to provide caching support using say, Enterprise Library Caching, Velocity, or Memcached. For this example, assume there's a class called EnterpriseLibraryCache
which implements ICache
.
Let’s test it. We will ensure that:
Given a new PageRepository
and an empty cache,
When GetPageById
is called with a PageId
,
Then it checks the cache first. If it finds nothing, it loads the page from database, and returns the page as expected.
The above statement is a behavior. Let’s code it using xUnit
and Subspec
:
[Specification]
public void GetPage_Should_Return_A_Page_from_database_when_cache_is_empty_and_then_caches_it()
{
var cache = new Mock<ICache>();
var database = new Mock<IDropthingsDataContext>();
IPageRepository pageRepository = new PageRepository(database.Object, cache.Object);
const int pageId = 1;
var page = default(Page);
var samplePage = new Page() { ID = pageId, Title = "Test Page", ...};
database
.Expect<Page>(d => d.GetSingle<Page, int>(
DropthingsDataContext.SubsystemEnum.Page,
1, LinqQueries.CompiledQuery_GetPageById))
.Returns(samplePage);
"Given PageRepository and empty cache".Context(() =>
{
cache.Expect(c => c.Get(It.IsAny<string>())).Returns(default(object));
cache.Expect(c =>
c.Add(It.Is<string>(cacheKey =>
cacheKey == CacheSetup.CacheKeys.PageId(pageId)),
It.Is<Page>(cachePage =>
object.ReferenceEquals(cachePage, samplePage))))
.AtMostOnce().Verifiable();
});
"when GetPageById is called".Do(() =>
page = pageRepository.GetPageById(1));
"it checks in the cache first and finds nothing and then caches it".Assert(() =>
cache.VerifyAll());
"it loads the page from database".Assert(() =>
database.VerifyAll());
"it returns the page as expected".Assert(() =>
{
Assert.Equal<int>(pageId, page.ID);
});
}
First I set up the PageRepository
. It takes both a IDropthingsDataContext
and ICache
, so I mock it up using Moq, then set up a sample Page
object that will be used to do the test. I am setting an expectation on the database that PageRepository
will call the GetSingle
method to run a specific Linq to SQL query and pass the PageId
as 1 in order to retrieve a single Page
object. Moreover, the call to database.GetSingle(…)
is stubbed to return samplePage
. The great thing about mocking up is it saves you from writing dummy classes or stubs for your unit tests and you can not only write stubs but also validate that the stubs are called with the right input and that the right output is being produced.
Next I set up the context, "Given PageRepository
and an empty cache". PageRepository
is already there, so I just need to setup an empty cache. I do this by setting the expectation that whenever Get
is called, it will return null, thus simulating an empty cache. Another expectation is that there will be one and only one call to the Add
method to store the samplePage
object that is loaded from the database.
Next I call the GetPageById
method of PageRepository
. Once the call is done, I test the expectations for the cache object, which is a call to the Get
function and one and only one call to the Add
function, have been met or not. This ensures that GetPageById
is doing the caching as expected.
The next Assert
verifies that the expectation on the database has been met, which is to query the database to get a single Page
object by passing PageId
as 1. Then finally, the last Assert
checks if the returned page has the same ID as it was asked to return.
These tests ensure the expected behavior is satisfied.
What’s the point of the Unit Test?
At this point, many of you will wonder why are we writing three times the code to test only a couple of lines of straight forward obvious code? If we are stubbing calls to the database
and cache
, and PageRepository
is doing nothing but calling database
and cache
, what’s the point of doing the test when nothing is happening but calling some mock ups? If every single implementation detail is already known in the test code, what’s the point writing the test then?
I find writing unit tests pointless when the method under test does nothing but call other methods that are mocked up in the test. The unit test already knows exactly what other classes and methods will be called and the test mocks up all of them. It seems pointless to write code to test such methods. To me, writing such tests is pointless.
But if the method under test has some other logic inside it, then unit testing does make sense, since the unit test will test the logic inside the method and ensure the mock ups are getting called properly when the logic is applied. In the above example, whether to use the cache
or database
is decided within the method, so there is logic that can be tested. In such a case, unit testing does make sense.
If you aren't still convinced, consider an example of a scenario where these unit tests will save your life. Let's say you changed the way caching is done in the Data Access Layer. For example, I changed my code to use the AspectF library. This requires changes in the code for PageRepository
. After changing the code, I need to ensure that PageRepository
is still doing things as per the expected behavior. No matter what caching approach I use, it should not change the caching behavior: check the cache to make sure the requested object is not already in cache and then load from database and cache it. The changed GetPageById
method, after implementing AspectF, looks like this:
public Page GetPageById(int pageId)
{
return AspectF.Define
.Cache<Page>(_cacheResolver, CacheSetup.CacheKeys.PageId(pageId))
.Return<Page>(() =>
_database.GetSingle<Page, int>(DropthingsDataContext.SubsystemEnum.Page,
pageId, LinqQueries.CompiledQuery_GetPageById).Detach());
}
Here AspectF
is doing nothing but making my caching life easier. Now when I run the unit test, it passes fine.
It confirms that the behavior of PageRepository
hasn’t changed, despite the drastic change in its code. It is still doing what it is expected to do, so I can do refactoring and re-engineering without fear that I might break something that will be discovered too late at the cost of missed deliveries. Having proper unit tests gives you the confidence that even if you make drastic changes in the code, as long as your unit tests all pass, your system is problem free.
Next let's test that when the cache is full, it properly returns objects from the cache and not unnecessarily query the database. The following test will ensure that:
[Specification]
public void GetPage_Should_Return_A_Page_from_cache_when_it_is_already_cached()
{
var cache = new Mock<ICache>();
var database = new Mock<IDropthingsDataContext>();
IPageRepository pageRepository = new PageRepository(database.Object, cache.Object);
const int pageId = 1;
var page = default(Page);
var samplePage = new Page() { ID = pageId, Title = "Test Page",
ColumnCount = 3, LayoutType = 3, UserId = Guid.Empty, VersionNo = 1,
PageType = Enumerations.PageTypeEnum.PersonalPage,
CreatedDate = DateTime.Now };
"Given PageRepository and the requested page in cache".Context(() =>
{
cache.Expect(c => c.Get(CacheSetup.CacheKeys.PageId(samplePage.ID)))
.Returns(samplePage);
});
"when GetPageById is called".Do(() =>
page = pageRepository.GetPageById(1));
"it checks in the cache first and finds the object is in cache".Assert(() =>
{
cache.VerifyAll();
});
"it returns the page as expected".Assert(() =>
{
Assert.Equal<int>(pageId, page.ID);
});
}
This test is even simpler. The only difference is in the setup of Context
where we set an expectation that when requesting a specific page from the cache, it will return the samplePage
object. Mock
will throw an exception whenever any function is called for which there is no expectation set, so if the code tried to call anything on database
object or anything else on the cache
object, it would throw an exception, thus showing that it's not doing what it should not do. This ensures that we are doing a negative test as well.
Integration Testing using BDD
Integration testing means you are going to test some class while it is integrated with other classes and with infrastructure, like the database, file system, mail server, etc. Whenever you write an integration test, you test a component's behavior as it should be, without any mock up. Integration tests are easier to write since there is no mock up. Moreover, they give additional confidence that the code works as it should, since all the necessary components and dependencies are in place and are also being tested.
Let me show how I test my Business Facade layer. The Business Facade handles the orchestration of the data access component and all other utility components. It encapsulates user actions into one business operation. For example, on Dropthings
, when a brand new user visits for the first time, the user gets default pages and widgets created. These pages and widgets come from a template. There is a user named anon_user@dropthings.com, who owns the default pages and widgets. That particular user's pages and widgets are copied to every new user. Since this is a complex operation, it was a good candidate to do automated integration testing.
When a user visits the Default.aspx
for the first time, the FirstVisitHomePage
is called on the Facade
. It goes through a complex process to clone the template pages and widgets and set the default user settings, etc. The integration test will ensure that if the FirstVisitHomePage
is called with parameters that identifies a new user visiting the site, then it will return an object which has the default pages and widgets created for the user. Thus the behavior is:
Given an anonymous user who has never visited the site before,
When the user visits for the first time,
Then create widgets on the newly created pages at the exact columns and positions as the anon_user's pages.
public class TestUserVisit
{
public TestUserVisit()
{
Facade.BootStrap();
}
[Specification]
public void First_visit_should_create_same_pages_and_widgets_as_the_template_user()
{
MembershipHelper.UsingNewAnonUser((profile) =>
{
using (var facade = new Facade(new AppContext(string.Empty, profile.UserName)))
{
UserSetup userVisitModel = null;
string anonUserName = facade.GetUserSettingTemplate()
.AnonUserSettingTemplate.UserName;
var anonPages = facade.GetPagesOfUser(facade.GetUserGuidFromUserName(anonUserName));
"Given anonymous user who has never visited the site before"
.Context(() => { });
"when the user visits for the first time".Do(() =>
{
userVisitModel = facade.FirstVisitHomePage(profile.UserName,
string.Empty, true, false);
});
"it creates widgets on the newly created page at exact columns and
positions as the anon user's pages".Assert(() =>
{
anonPages.Each(anonPage =>
{
var userPage = userVisitModel.UserPages.First(page =>
page.Title == anonPage.Title
&& page.OrderNo == anonPage.OrderNo
&& page.PageType == anonPage.PageType);
facade.GetColumnsInPage(anonPage.ID).Each(anonColumn =>
{
var userColumns = facade.GetColumnsInPage(userPage.ID);
var userColumn = userColumns.First(column =>
column.ColumnNo == anonColumn.ColumnNo);
var anonColumnWidgets =
facade.GetWidgetInstancesInZoneWithWidget(anonColumn.WidgetZoneId);
var userColumnWidgets =
facade.GetWidgetInstancesInZoneWithWidget(userColumn.WidgetZoneId);
anonColumnWidgets.Each(anonWidget =>
Assert.True(userColumnWidgets.Where(userWidget =>
userWidget.Title == anonWidget.Title
&& userWidget.Expanded == anonWidget.Expanded
&& userWidget.State == anonWidget.State
&& userWidget.Resized == anonWidget.Resized
&& userWidget.Height == anonWidget.Height
&& userWidget.OrderNo == anonWidget.OrderNo).Count() == 1));
});
});
});
}
});
}
I will not go through all the boring details of how the code works since the comments should serve to explain, but the last Assert
is complicated and requires further explanation:
- For each page found from the template user
- Ensure the new user gets the exact same page
- Get the widgets from the template user's page.
- Get the widgets from the new user's page
- Compare each widget. For each widget
- Ensure there’s one and only widget which has the same title, state, position etc.
Whenever I make changes in the business layer, I can run the integration tests to ensure the key features are working as expected and that I haven’t broken anything anywhere in the whole business layer. I use xunit.console.exe to run the tests on the integration test and it generates a nice html
report:
The report is generated using the following command line:
d:\xunit\xunit.console.exe
d:\trunk\src\Dropthings.Business.Facade.Tests\bin\Debug\Dropthings.Business.Facade.Tests.dll
/html FacadeTest.html
You can use the xUnit GUI as well:
Test Driven Development using BDD for Unit Testing
So far we have had code before writing the tests, but what about test driven development where you write the tests first and then the code? Assume we want to add behavior:
Given a PageRepository
,
When Insert
is called,
Then it should insert the page in database, clear any cached collection of pages for the user who gets the new page, and return the newly inserted page.
Let's write the test first.
[Specification]
public void InsertPage_should_insert_a_page_in_database_and_cache_it()
{
var cache = new Mock<ICache>();
var database = new Mock<IDropthingsDataContext>();
IPageRepository pageRepository = new PageRepository(database.Object, cache.Object);
const int pageId = 1;
var page = default(Page);
var samplePage = new Page() { ID = pageId, Title = "Test Page", ColumnCount = 3,
LayoutType = 3, UserId = Guid.NewGuid(), VersionNo = 1,
PageType = Enumerations.PageTypeEnum.PersonalPage, CreatedDate = DateTime.Now };
database
.Expect<Page>(d => d.Insert<Page>(DropthingsDataContext.SubsystemEnum.Page,
It.IsAny<Action<Page>>()))
.Returns(samplePage);
"Given PageRepository".Context(() =>
{
cache.Expect(c => c.Remove(CacheSetup.CacheKeys.PagesOfUser(samplePage.UserId)));
});
"when Insert is called".Do(() =>
page = pageRepository.Insert((newPage) =>
{
newPage.Title = samplePage.Title;
newPage.ColumnCount = samplePage.ColumnCount;
newPage.LayoutType = samplePage.LayoutType;
newPage.UserId = samplePage.UserId;
newPage.VersionNo = samplePage.VersionNo;
newPage.PageType = samplePage.PageType;
}));
("then it should insert the page in database" +
"and clear any cached collection of pages for the user who gets the new page" +
"and it returns the newly inserted page").Assert(() =>
{
database.VerifyAll();
cache.VerifyAll();
Assert.Equal<int>(pageId, page.ID);
});
}
First we will write some dummy code in the PageRepository.Insert
method to do nothing but return a new Page
. It should fail because it won't currently meet the expectation set on database object. If it does not fail, our test is wrong.
public Page Insert(Action<Page> populate)
{
return new Page();
}
Running the test results in a failure as expected:
TestCase 'Given PageRepository when InsertPage is called, then it should insert the
page in databaseand clear any cached collection of pages for the user who gets the
new pageand it returns the newly inserted page'
failed: Moq.MockVerificationException : The following expectations were not met:
IDropthingsDataContext d => d.Insert(Page, null)
at Moq.Mock`1.VerifyAll()
PageRepositoryTest.cs(278,0): at
Dropthings.DataAccess.UnitTest.PageRepositoryTest.<>c__DisplayClass35.
<InsertPage_should_insert_a_page_in_database_and_cache_it>b__34()
This shows that there was no call to database.Insert
, so the test failed. We achieved the first pillar of TDD, which is to write a test and make it fail first since the expectations are not properly made by the component under test.
Now let's add the real code:
public Page Insert(Action<Page> populate)
{
var newPage = _database.Insert<Page>(
DropthingsDataContext.SubsystemEnum.Page, populate);
RemoveUserPagesCollection(newPage.UserId);
return newPage.Detach();
}
Now when the unit tests are on PageRepository
, it passes the new test, along with previous tests:
Cool! We have achieved Test Driven Development using Behavior Driven Development.
Test Driven Development using BDD for Integration Testing
In the previous section we performed a Unit Test. What if we want to do TDD for Integration Testing? How do we first write the test and then write the business layer code which is integrated with the other components? How do we do TDD for the web layer?
The approach is same – first you write the test code that gives the right input and expects the right output from the business operation, however, integration tests should not just call one business operation in isolation to ensure that it works without throwing an exception. Integration testing should also ensure the right behavior occurs when performing other operations. For example, when testing FirstVisitHomePage
, the expectation is that after the first visit, the user has the correct pages created. The test code verifies this by checking the returned object model, but the real world scenario is that after the first visit, if the user returns, they should see the same widgets. That would prove that the first visit operation was successful, but I haven’t yet done a repeat visit to confirm the first and subsequent visit returns the same data. I should also test the following:
[Specification]
public void Revisit_should_load_the_pages_and_widgets_exactly_the_same()
{
MembershipHelper.UsingNewAnonUser((profile) =>
{
using (var facade = new Facade(new AppContext(string.Empty, profile.UserName)))
{
UserSetup userVisitModel = null;
UserSetup userRevisitModel = null;
"Given an anonymous user who visited first".Context(() =>
{
userVisitModel = facade.FirstVisitHomePage(profile.UserName, ...);
});
"when the same user visits again".Do(() =>
{
userRevisitModel = facade.RepeatVisitHomePage(profile.UserName, ...);
});
"it should load the exact same pages, column and
widgets as the first visit produced".Assert(() =>
{
userVisitModel.UserPages.Each(firstVisitPage =>
{
Assert.True(userRevisitModel.UserPages.Exists(page =>
page.ID == firstVisitPage.ID));
var revisitPage = userRevisitModel.UserPages.First(page =>
page.ID == firstVisitPage.ID);
var revisitPageColumns = facade.GetColumnsInPage(revisitPage.ID);
facade.GetColumnsInPage(firstVisitPage.ID).Each(firstVisitColumn =>
{
var revisitColumn = revisitPageColumns.First(column =>
column.ID == firstVisitColumn.ID);
var firstVisitWidgets = facade
.GetWidgetInstancesInZoneWithWidget(firstVisitColumn.WidgetZoneId);
var revisitWidgets = facade
.GetWidgetInstancesInZoneWithWidget(revisitColumn.WidgetZoneId);
firstVisitWidgets.Each(firstVisitWidget =>
Assert.True(revisitWidgets.Where(revisitWidget =>
revisitWidget.Id == firstVisitWidget.Id).Count() == 1));
});
});
});
}
});
}
The right way to do integration tests is the opposite of writing unit tests. In a unit test, the approach is to test one and only one class in complete isolation by calling one method and stubbing out or mocking up everything else. In integration tests, you should test not only just one operation, but also perform other related operations to ensure that the operation under test really does what it is expected to do. I have generalized the possible test cases into these categories:
- When testing an operation that creates new data (for example, insert rows in a database or call a webservice to create an entity), ensure the operation was carried out properly by:
- Calling other operations that read the data by reading the row again or calling another webservice to get the created entity. It should fail if the data was not inserted properly (for example, insert child rows). This is a positive test.
- Calling other operations that would fail if the insert was successful, for example inserting the same row again would produce a constraint violation. This is a negative test.
- When you test an operation that updates data (for example, update rows in database), ensure the operation updated the data properly by
- Calling other operations that use the updated data and would fail if the data was not updated properly, for example make two consecutive money transfers where there is insufficient balance in an account after the first money transfer). This is a positive test.
- Calling other operations that would fail if the update was successful, for example trying to insert a new row in a database using the same values after an update should produce constraint violations. This is a negative test.
- When you test an operation that deletes some data, ensure the operation deleted the data properly by
- Calling other operations that would fail if the data exists, for example insert the same row again to produce a constraint violation.
- Calling other operations that would fail if the data was deleted properly, for example inserting child rows for the non-existent row.
It is important to do both positive and negative tests in integration tests, even if you are doing it in unit tests to ensure that the tests cover all major behaviors of the system. One benefit of integration tests is it tests the unpredictability of the infrastructure more than testing your own code, assuming your own code is well unit tested already. It is important that as many positive and negative scenarios as possible are covered to rule out infrastructure variables.
Conclusion
I hope this article has given some examples of realistic unit and integration testing using an approach that I find useful. There are many ways you can do TDD and you don’t need to strictly follow a certain approach all the time. I found that using BDD to do TDD was the most effective approach. This technique reduces the number of unit test methods that I have to write to test the complete behavior of the components. Doing BDD using xUnit
and Subspec
makes test code more readable and more natural to write.