Source is here: https://github.com/techtalk
SpecFlow aims at bridging the communication gap between domain experts and developers by binding business readable behavior specifications to the underlying implementation.
http://stackoverflow.com/questions/3443302/specflow-bdd-examples
https://github.com/techtalk/SpecFlow/wiki/Getting-Started – lots of links to tutorials and videos
App has only a few screens. To see all books, you must search for nothing.
Testing the HomeController of the MVC layer
- It returns a result to the default view
- It produces a list of books in the Model (which the view will use).. using a faked out Service layer which just returns the 2 books
Details
Code Snippet
[TestMethod]
public void WhenValidBookId_ShouldReturnViewWithBookForTheId()
{
// given
var book = new Book();
catalogServicesFake.Setup(m => m.GetBookDetails(1)).Returns(book);
// when
var result = controller.Details(1);
// then
var model = result.GetModel<DetailsModel>();
Assert.AreEqual(book, model.Book);
}
[TestMethod, ExpectedException(typeof(ArgumentException))]
public void WhenInvalidBookId_ShouldThrowArgumentException()
{
// given
catalogServicesFake.Setup(m => m.GetBookDetails(1)).Returns((Book)null);
// when
controller.Details(1);
// then: ArgumentException
}
- First test just tests that the /catalog/details/1 actually returns a viewresult ie something happens
- second just fakes a book
- third is testing the null catch – this is actually testing business logic, and not just things are wired up right.
Code Snippet
public ActionResult Details(int id)
{
var book = catalogServices.GetBookDetails(id);
if (book == null)
throw new ArgumentException("Invalid book id", "id");
var ratings = this.ratingsGateway.RetrieveRatings(book);
decimal averageRating = ratingCalculator.CalculateRating(ratings ?? new Rating[0]);
// US1_2_1: Call recommendationsGateway and process result
string recommendedBooks = "";
return View(new DetailsModel {
Book = book,
Rating = averageRating,
RecommendedBooks = recommendedBooks
});
}
Search
Unit tests at this level (MVC) are really just testing something happens and doesn’t blow up. And guard conditions are working ie null checks.
Code Snippet
namespace BookShop.UnitTests.CatalogControllerTests {
[TestClass]
public class SearchTests {
private CatalogController controller;
private Mock<ICatalogServices> catalogServicesFake;
[TestInitialize]
public void Setup() {
catalogServicesFake = new Mock<ICatalogServices>();
controller = new CatalogController(
catalogServicesFake.Object,
new Mock<IRatingsGateway>().Object,
new Mock<IRatingCalculator>().Object,
new Mock<IRecommendationsGateway>().Object);
}
[TestMethod]
public void WhenSearchIsSuccessful_ShouldReturnViewResultToListView() {
// given
catalogServicesFake.Setup(m => m.Search("searchTerm")).Returns(new List<Book>());
// when
var result = controller.Search("searchTerm");
// then
Assert.AreEqual("List", ((ViewResult)result).ViewName);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void WhenArgumentIsNull_ShouldThowException() {
// given
// when
controller.Search(null);
// then: ArgumentNullException
}
[TestMethod]
public void WhenSearchResultIsEmpty_ShouldReturnViewWithEmptyList() {
// given
catalogServicesFake.Setup(m => m.Search("nonMatchingSearchTerm")).Returns(new List<Book>());
// when
var result = controller.Search("nonMatchingSearchTerm");
// then
var model = result.GetModel<List<Book>>();
Assert.AreEqual(0, model.Count);
}
[TestMethod]
public void WhenSearchResultIsNotEmpty_ShouldReturnViewWithListOfBooks() {
// given
var book1 = new Book();
var book2 = new Book();
catalogServicesFake.Setup(m => m.Search("searchTerm")).Returns(new List<Book> { book1, book2 });
// when
var result = controller.Search("searchTerm");
// then
var model = result.GetModel<List<Book>>();
CollectionAssert.AreEquivalent(new List<Book> { book1, book2 }, model);
}
}