I started writing a set of tests for my SageContextBase
class, which needs to be abstract
because the client code must derive from it (think of it as EF's DbContext
- it can't be instantiated directly, but it packs a truckload of functionality) - I have 5 passing tests covering everything that should be happening inside the Open
method:
[TestClass]
public class SageContextBaseOpenTests
{
private Mock<IView> GetMockView(string viewId)
{
var view = new Mock<IView>();
view.SetupAllProperties();
view.SetupGet(m => m.ViewId).Returns(viewId);
view.Setup(m => m.Compose(It.IsAny<IView[]>()));
view.Setup(m => m.Dispose());
view.Setup(m => m.Insert());
view.Setup(m => m.Delete());
view.Setup(m => m.Update());
view.Setup(m => m.FilterCount(It.IsAny<string>()));
return view;
}
private Mock<ISession> GetMockSession()
{
var dbLink = new Mock<IDbLink>();
dbLink.SetupAllProperties();
const string headersViewId = "PO0620";
var headersView = GetMockView(headersViewId);
dbLink.Setup(m => m.OpenView(headersViewId)).Returns(headersView.Object);
const string detailsViewId = "PO0630";
var detailsView = GetMockView(detailsViewId);
dbLink.Setup(m => m.OpenView(detailsViewId)).Returns(detailsView.Object);
var session = new Mock<ISession>();
session.SetupAllProperties();
session.Setup(m => m.OpenDbLink(It.IsAny<DBLinkType>(), It.IsAny<DBLinkFlags>())).Returns(dbLink.Object);
return session;
}
[TestMethod]
public void Open_InitializesSessionWithSpecifiedAppInfo()
{
// arrange
var credentials = new SageCredential("testuser", "testpwd", "testdb");
var appInfo = new SageAppInfo("testapp", "v42");
var session = GetMockSession();
var context = new TestSageContext(credentials, appInfo, session.Object);
// act
context.Open();
// assert
session.Verify(m => m.Init(It.IsAny<string>(), appInfo));
}
[TestMethod]
public void Open_OpensSessionWithSpecifiedCredentials()
{
// arrange
var credentials = new SageCredential("testuser", "testpwd", "testdb");
var appInfo = new SageAppInfo("testapp", "v42");
var session = GetMockSession();
var context = new TestSageContext(credentials, appInfo, session.Object);
// act
context.Open();
// assert
session.Verify(m => m.Open(credentials, It.IsAny<DateTime>(), It.IsAny<int>()));
}
[TestMethod]
public void Open_OpensDbLinkWithCompanyAndReadWriteFlags()
{
// arrange
var credentials = new SageCredential("testuser", "testpwd", "testdb");
var appInfo = new SageAppInfo("testapp", "v42");
var session = GetMockSession();
var context = new TestSageContext(credentials, appInfo, session.Object);
// act
context.Open();
// assert
session.Verify(m => m.OpenDbLink(DBLinkType.Company, DBLinkFlags.ReadWrite));
}
[TestMethod]
public void Open_CreatesViewSetInstances()
{
// arrange
var credentials = new SageCredential("testuser", "testpwd", "testdb");
var appInfo = new SageAppInfo("testapp", "v42");
var session = GetMockSession();
var context = new TestSageContext(credentials, appInfo, session.Object);
if (context.ViewSets.Any())
{
Assert.Inconclusive("Context.ViewSets collection must initialize empty.");
}
var properties = typeof (TestSageContext).GetProperties()
.Where(property => property.PropertyType.IsGenericType
&& property.CanRead && property.CanWrite
&& property.GetMethod.IsPublic
&& property.PropertyType.IsGenericType
&& property.PropertyType.GetGenericTypeDefinition().Name == typeof(ViewSet).Name + "`1")
.ToList();
var expected = properties.Count;
// act
context.Open();
// assert
Assert.AreEqual(expected, context.ViewSets.Count);
}
[TestMethod]
public void Open_CallsOnModelCreating()
{
// arrange
var credentials = new SageCredential("testuser", "testpwd", "testdb");
var appInfo = new SageAppInfo("testapp", "v42");
var session = GetMockSession();
var context = new TestSageContext(credentials, appInfo, session.Object);
// act
context.Open();
// assert
Assert.IsTrue(context.IsModelComposed);
}
}
These tests use a fake implementation of the abstract class I'm testing:
class TestSageContext : SageContextBase
{
public TestSageContext(SageCredential credential, SageAppInfo appInfo, ISession session)
: base(credential, appInfo, session)
{
}
public ViewSet<PO0620> PurchaseOrderHeaders { get; set; }
public ViewSet<PO0630> PurchaseOrderDetails { get; set; }
public bool IsModelComposed { get; private set; }
protected override void OnModelComposing()
{
PurchaseOrderHeaders.Compose(null, PurchaseOrderDetails, null, null, null);
PurchaseOrderDetails.Compose(PurchaseOrderHeaders, null, null, null, null, null);
IsModelComposed = true;
}
}
Here's the method under test, and a few relevant members - note that the entire class is up for review here:
/// <summary>
/// Opens the database connection and initializes the <see cref="ViewSet{TEntity}"/> members.
/// </summary>
public void Open()
{
_session.Init(string.Empty, _info);
_session.Open(_credential, DateTime.Now);
_db = _session.OpenDbLink(DBLinkType.Company, DBLinkFlags.ReadWrite);
var properties = GetType().GetProperties()
.Where(p => p.CanRead && p.CanWrite
&& p.PropertyType.IsGenericType
&& p.CanRead
&& p.CanWrite
&& p.GetMethod.IsPublic
&& p.PropertyType.IsGenericType
&& p.PropertyType.GetGenericTypeDefinition().Name == typeof (ViewSet).Name + "`1");
foreach (var propertyInfo in properties)
{
var entityType = propertyInfo.PropertyType.GenericTypeArguments[0];
var view = AddView(GetMappedViewId(entityType));
var constructedType = typeof (ViewSet<>).MakeGenericType(entityType);
var viewSetInstance = (ViewSet)Activator.CreateInstance(constructedType, view, this);
propertyInfo.SetValue(this, viewSetInstance);
_viewSets.Add(viewSetInstance);
}
OnModelComposing();
}
private readonly IList<ViewSet> _viewSets = new List<ViewSet>();
public IReadOnlyList<ViewSet> ViewSets { get { return _viewSets.ToList(); } }
/// <summary>
/// Provides a hook for derived types to specify how views are composed.
/// </summary>
protected abstract void OnModelComposing();
I'm particularly interested in the implementation of the last test, which verifies whether the OnModelComposing
method was called... but as always, any & all feedback is welcome.
Open_CallsOnModelCreating
looks like it should really beOpen_CallsOnModelComposing
something to watch during refactoring possibly... \$\endgroup\$