Unit Testing a Class With A Private Constructor - c#

I am trying to test a class that only has a private constructor. This is for a course registration system. The courses do not get create via our application, therefore we intentionally have no public constructor. Instead we use EF to get the courses that are already in the database, and register students to them.
I am trying to test the register method of the Course class, however I have no way of creating an instance. I could use
course = (Course)Activator.CreateInstance(typeof(Course), true);, but then I don't have a way to setup the necessary properties since those are private.
What is the recommended approach for unit testing without a constructor?
This is a slimmed down version of the code.
public class Course
{
private Course()
{
}
public int Id { get; private set; }
public string Name { get; private set; }
public bool Open { get; private set; }
public virtual ICollection<Student> Students { get; private set; }
public void Register(string studentName)
{
if (Open)
{
var student = new Student(studentName);
Students.Add(student);
}
}
}
// Usage //
using (var db = new SchoolContext())
{
var course = db.Courses.Include(x => x.Students).Where(x => x.Name == courseName).First();
course.Register(studentName);
db.SaveChanges();
}
// Unit Test //
[Fact]
public void CanRegisterStudentForOpenClass(){
// HERE I HAVE NO WAY TO CHANGE THE OPEN VARIABLE
var course = (Course)Activator.CreateInstance(typeof(Course), true);
course.Register("Bob");
}

Yes you can using reflexion. your code is neraly there;
you can get properties and fields of the types with typeof(Course).GetProperty("PropertyName") then you can use SetValue to set the desired value, and pass as parameter first the instance to modify then the value.
in your case true;
note: in your example you will need to add the Collection of students too, if your Open is true.
Here there is a working example:
[Fact]
public void CanRegisterStudentForOpenClass()
{
var course = (Course)Activator.CreateInstance(typeof(Course), true);
typeof(Course).GetProperty("Open").SetValue(course, true, null);
ICollection<Student> students = new List<Student>();
typeof(Course).GetProperty("Students").SetValue(course, students, null);
course.Register("Bob");
Assert.Single(course.Students);
}

If you would rather not use reflection, then I recommend you use internal classes (instead of private) and using the InternalsVisibleToAttribute on your implementation assembly.
You can find more about the attribute here. Here's a quick guide on how you can use it!
Step 1. Add this attribute to your assembly that wants its internal code tested.
[assembly: InternalsVisibleToAttribute("MyUnitTestedProject.UnitTests")]
Step 2. Change private to internal.
public class Course
{
internal Course()
{
}
public int Id { get; internal set; }
public string Name { get; internal set; }
public bool Open { get; internal set; }
public virtual ICollection<Student> Students { get; internal set; }
/* ... */
}
Step 3. Write your tests like normal!
[Fact]
public void CanRegisterStudentForOpenClass()
{
var course = new Course();
course.Id = "#####";
course.Register("Bob");
}

As a few people have mentioned here, unit testing something private is either a code smell, or a sign you're writing the wrong tests.
In this case, what you would want to do is use EF's in-memory database if you're using Core, or mocking with EF6.
For EF6 You can follow the docs here
I would say rather than newing your dbContext where you do, pass it in via Dependency Injection. If that's beyond the scope of the work you're doing, (I'm assuming this is actual coursework, so going to DI may be overkill) then you can create a wrapper class that takes a dbcontext and use that in place.
Taking a few liberties with where this code is called from...
class Semester
{
//...skipping members etc
//if your original is like this
public RegisterCourses(Student student)
{
using (var db = new SchoolContext())
{
RegisterCourses(student, db);
}
}
//change it to this
public RegisterCourses(Student student, SchoolContext db)
{
var course = db.Courses.Include(x => x.Students).Where(x => x.Name == courseName).First();
course.Register(studentName);
db.SaveChanges();
}
}
[Fact]
public void CanRegisterStudentForOpenClass()
{
//following after https://learn.microsoft.com/en-us/ef/ef6/fundamentals/testing/mocking#testing-query-scenarios
var mockCourseSet = new Mock<DbSet<Course>>();
mockCourseSet.As<IQueryable<Course>>().Setup(m => m.Provider).Returns(data.Provider);
mockCourseSet.As<IQueryable<Course>>().Setup(m => m.Expression).Returns(data.Expression);
mockCourseSet.As<IQueryable<Course>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockCourseSet.As<IQueryable<Course>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
//create an aditional mock for the Student dbset
mockStudentSet.As.........
var mockContext = new Mock<SchoolContext>();
mockContext.Setup(c => c.Courses).Returns(mockCourseSet.Object);
//same for student so we can include it
mockContext.Include(It.IsAny<string>()).Returns(mockStudentSet); //you can change the isAny here to check for Bob or such
var student = Institution.GetStudent("Bob");
var semester = Institution.GetSemester(Semester.One);
semester.RegisterCourses(student, mockContext);
}
If you're using EFCore you can follow it along from here

You can fake private constructors and members using TypeMock Isolator or JustMock (both paid) or using MS Fakes (only available in VS Enterprise).
There is also a free Pose library that allows you to fake access to properties.
Unfortunately, the private constructor can't be forged. Therefore, you will need to create an instance of the class using reflection.
Add package.
Open namespace:
using Pose;
Test code:
[Fact]
public void CanRegisterStudentForOpenClass()
{
var course = (Course)Activator.CreateInstance(typeof(Course), true);
ICollection<Student> students = new List<Student>();
Shim studentsPropShim = Shim.Replace(() => Is.A<Course>().Students)
.With((Course _) => students);
Shim openPropShim = Shim.Replace(() => Is.A<Course>().Open)
.With((Course _) => true);
int actual = 0;
PoseContext.Isolate(() =>
{
course.Register("Bob");
actual = course.Students.Count;
},
studentsPropShim, openPropShim);
Assert.Equal(1, actual);
}

You can create a JSON representation of your default instance and deserialize it with Newtonsoft.
Something like this:
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using privateConstructor;
namespace privateConstructorTest
{
[TestClass]
public class CourseTest
{
[TestMethod]
public void Register_WhenOpenIsTrue_EnableAddStudents()
{
// Arrange
const string json = #"{'Id': 1, 'name':'My Course', 'open':'true', 'students':[]}";
var course = CreateInstance<Course>(json);
// Act
course.Register("Bob");
// Assert
Assert.AreEqual(1, course.Students.Count);
}
[TestMethod]
public void Register_WhenOpenIsFalse_DisableAddStudents()
{
// Arrange
const string json = #"{'Id': 1, 'name':'My Course', 'open':'false', 'students':[]}";
var course = CreateInstance<Course>(json);
// Act
course.Register("Bob");
// Assert
Assert.AreEqual(0, course.Students.Count);
}
private static T CreateInstance<T>(string json) =>
JsonConvert.DeserializeObject<T>(json, new JsonSerializerSettings
{
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
ContractResolver = new ContractResolverWithPrivates()
});
public class ContractResolverWithPrivates : CamelCasePropertyNamesContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var prop = base.CreateProperty(member, memberSerialization);
if (prop.Writable) return prop;
var property = member as PropertyInfo;
if (property == null) return prop;
var hasPrivateSetter = property.GetSetMethod(true) != null;
prop.Writable = hasPrivateSetter;
return prop;
}
}
}
}
In order to have a cleaner test class, you can extract the JSON strings and the helper code that creates the instance.

Related

Mock custom implementation of CodeAccessSecurityAttribute

I have a custom implementation of CodeAccessSecurityAttribute that is connecting external sources to do a validation.
[Serializable]
[AttributeUsage(AttributeTargets.Method)]
public class IsAuthorizedAttribute : CodeAccessSecurityAttribute
{
private static readonly PrincipalPermission Allowed = new PrincipalPermission(PermissionState.None);
private static readonly PrincipalPermission NotAllowed = new PrincipalPermission(PermissionState.Unrestricted);
public string EntityObject { get; set; }
public string Field { get; set; }
public char Expected { get; set; }
public IsAuthorizedAttribute(SecurityAction action)
: base(action)
{
//setup
}
public override IPermission CreatePermission()
{
return IsAuthorised(EntityObject, Field, Expected, ServicesConfiguration) ? Allowed : NotAllowed;
}
private static bool IsAuthorised(string entityObject, string field, char expected, ServicesConfiguration servicesConfiguration)
{
bool? response = null;
//check external stuff
return response ?? false;
}
}
I have decorated my methods with this attribute:
[IsAuthorized(SecurityAction.Demand, EntityObject = Fields.UserManagement, Field = Fields.AllowDisplay, Expected = '1')]
public List<Group> GetUserGroups()
{
var response = new List<Group>();
//Get the groups from the database
var groups = groupManager.FindAll();
//Map them to the output group type
response = groups.Select(x => new Group()
{
ID = x.ID,
Name = x.Name,
Alias = x.Alias,
Description = x.Description
}).ToList();
return response;
}
I now want to unit test this method, but the attribute is fired. I have tried some things to mock the attribute, but without success.
I'm using Moq and Smocks.
This is my unit test without a mocked instance of the attribute:
[TestMethod]
public void GetUserGroups_UserGroupsFound_UserGroupsReturned()
{
Smock.Run(context =>
{
//Arrange
Setup();
m_Container
.RegisterMock<IGroupManager>()
.Setup(x => x.FindAllFromCache())
.Returns(new List<Concept.Security.MasterData.Domain.Group>()
{
new Concept.Security.MasterData.Domain.Group()
{
Name = "MyUserGroup",
Alias = "My User Group",
Description = "My user group description",
System = false,
Authorizations = "000001111100000000"
},
new Concept.Security.MasterData.Domain.Group()
{
Name = "MySecondUserGroup",
Alias = "My Second User Group",
Description = "My second user group description",
System = false,
Authorizations = "000000000000000000"
}
});
var identityService = new UserManagementService(m_Container, m_UserAuthorizationManager.Object, m_IdentityService.Object);
//** begin add mocked attribute **//
//** end add mocked attribute **//
//Act
var response = identityService.GetUserGroups();
//Assert
Assert.AreEqual(2, response.Count);
Assert.AreEqual(1, response.Where(x => x.Alias == "MyUserGroup").Count());
Assert.AreEqual(1, response.Where(x => x.Alias == "MySecondUserGroup").Count());
Assert.AreEqual(2, response.Where(x => x.Authorizations == null).Count());
});
}
Running this results in an exception because the attribute tries to connect the external services and they aren't (and can't be) setup to receive requests.
So, I try to add a mocked attribute:
//** begin add mocked attribute **//
var identityService = new UserManagementService(m_Container, m_UserAuthorizationManager.Object, m_IdentityService.Object);
var IsAuthorizedAttribute = new Mock<IsAuthorizedAttribute>(MockBehavior.Strict, new object[] { SecurityAction.Demand });
IsAuthorizedAttribute.Setup(x => x.CreatePermission()).Returns(new PrincipalPermission(PermissionState.None));
TypeDescriptor.AddAttributes(identityService, IsAuthorizedAttribute.Object);
//** end add mocked attribute **//
But this one is calling the constructor of the attribute where I set up the external source. When I put this constructor in a try/catch and silently disposing the exception, I have an error on IsAuthorizedAttribute.Object object can't be found.
What are other options to not fire the attribute?
Constructors should not access externals; otherwise it will be difficult to bypass for testing, as you know.
A simple way is to make static bool field to bypass. This does not look so good but maybe enough.
public class IsAuthorizedAttribute : CodeAccessSecurityAttribute
{
// set true in the test initialization
private static bool s_byPass;
public IsAuthorizedAttribute(SecurityAction action) : base(action)
{
if (!s_byPass)
{
// setup
}
}
private static bool IsAuthorised(string entityObject, string field, char expected, ServicesConfiguration servicesConfiguration)
{
if (s_byPass) { return true; }
//check external stuff
}
}
Another better approach is to extract the external dependency to another class so that you can mock it. Mocking external dependencies is a typical pattern of a unit test.
public class IsAuthorizedAttribute : CodeAccessSecurityAttribute
{
// set mock here in the test initialization.
// I assume external accessor can be a static field.
private static ExternalAccessor m_accessor = new ExternalAccessor();
private static bool IsAuthorised(string entityObject, string field, char expected, ServicesConfiguration servicesConfiguration)
{
return m_accessor.Check();
}
}
public class ExternalAccessor
{
private bool m_initialized;
private void Setup()
{
// setup
m_initialized = true;
}
public virtual bool Check()
{
// You can call setup anytime but the constructor.
if (!m_initialized) { Setup(); }
// check external stuff
}
}

Moq mocked DbContext returns null ObjectContext

I have a custom DisconnectedDbContext for use with self state tracking POCOs in a web app.
public abstract class DisconnectedDbContext : DbContext
{
protected DisconnectedDbContext()
{
var objAdapterContext = ((IObjectContextAdapter)this).ObjectContext;
}
}
I subclass this for some unit testing:
public class FruityContext : DisconnectedDbContext
{
public virtual DbSet<FruitBowl> FruitBowls { get; set; }
public virtual DbSet<Fruit> Fruits { get; set; }
}
And using Moq in a TestMethod as below:
[TestMethod]
public void CreateAFruityContext()
{
var dbc = new FruityContext();
Assert.IsNotNull(dbc);
var mockSet = new Mock<DbSet<FruitBowl>>();
var mockContext = new Mock<FruityContext>();
mockContext.Setup(m => m.FruitBowls).Returns(mockSet.Object);
var mo = mockContext.Object;
Assert.IsNotNull(mo);
}
Now this is not an actual TestMethod so I don't want to get sidetracked about that.
My problem is that for the creation of var dbc in this method, objAdapterContext in the constructor call is not null however for var mo it is null. I need objAdapterContext to be not null, as per non-mocked objects because I tap into this to handle the ObjectMaterialized event of the ObjectContext.
So the Moq wrapper is changing the behaviour of my code. Is there something I can do about this?
Use this:
mockSet.CallBase = true;
mockContext.CallBase = true;

Moq - Extracting Mock DataSet & Context to a Separate Class

I have a unit test which verifies that a function adds exactly one record to an Entity Framework dataset like so:
var internals = new Mock<DbSet<InternalTransaction>>();
internals.Setup(q => q.Create()).Returns(new InternalTransaction());
var mockDC = new Mock<IDataContext>();
mockDC.Setup(q => q.InternalTransactions).Returns(internals.Object);
// Removed: set up some data for my test
mockDC.Object.DoSomeFunction(data);
internals.Verify(e => e.Add(It.IsAny<InternalTransaction>()), Times.Once());
Since I have a lot of tests to do with this context, I'm trying to extract the mock stuff to a separate class, like so:
public static class DataContext_Creators
{
public static Mock<IDataContext> CreateMockContext()
{
var internals = CreateMockDataSet<InternalTransaction>();
var externals = CreateMockDataSet<ExternalTransaction>();
var mockDC = new Mock<IDataContext>();
mockDC.Setup(q => q.InternalTransactions).Returns(internals.Object);
mockDC.Setup(q => q.ExternalTransactions).Returns(externals.Object);
return mockDC;
}
private static Mock<DbSet<T>> CreateMockDataSet<T>() where T : class, new ()
{
var mockDataSet = new Mock<DbSet<T>>();
mockDataSet.Setup(q => q.Create()).Returns(new T());
// some other stuff, Provider, GetEnumerator, etc
return mockDataSet;
}
}
... and change my test to:
mockContext = Common.DataContext_Creators.CreateMockContext();
context = mockContext.Object;
// Removed: set up some data for my test
context.Object.DoSomeFunction(data);
//internals.Verify(e => e.Add(It.IsAny<InternalTransaction>()), Times.Once());
But now that I can't access "internals" anymore, how can I do the .Verify() statement? context.InternalTransactions won't work because that returns "internals.Object" (which it needs to for the method I'm testing to work).
From that object you may once again regain Mock object of it by using Mock.Get() method. Look at example:
public class Entity
{
public virtual int Id { get; set; }
public virtual int? ParentId { get; set; }
}
static void Main(string[] args)
{
Entity entity = Mock.Of<Entity>();
var mock = Mock.Get<Entity>(entity);
mock.Setup(e => e.ParentId).Returns(11);
var result = entity.ParentId;
// now result == 11
}
You could use Mock.Get method on your internals.Object
Create a new utility class with 2 properties:
Mock<IDataContext> DbContextMock
Mock<DbSet<InternalTransaction>> InternalTransMock
Have your Utility function return this new object so your test has access to both.

Creating a hybrid of a mock and an anonymous object using e.g. Moq and AutoFixture?

I encountered a class during my work that looks like this:
public class MyObject
{
public int? A {get; set;}
public int? B {get; set;}
public int? C {get; set;}
public virtual int? GetSomeValue()
{
//simplified behavior:
return A ?? B ?? C;
}
}
The issue is that I have some code that accesses A, B and C and calls the GetSomeValue() method (now, I'd say this is not a good design, but sometimes my hands are tied ;-)). I want to create a mock of this object, which, at the same time, has A, B and C set to some values. So, when I use moq as such:
var m = new Mock<MyObject>() { DefaultValue = DefaultValue.Mock };
lets me setup a result on GetSomeValue() method, but all the properties are set to null (and setting up all of them using Setup() is quite cumbersome, since the real object is a nasty data object and has more properties than in above simplified example).
So on the other hand, using AutoFixture like this:
var fixture = new Fixture();
var anyMyObject = fixture.CreateAnonymous<MyObject>();
Leaves me without the ability to stup a call to GetSomeValue() method.
Is there any way to combine the two, to have anonymous values and the ability to setup call results?
Edit
Based on nemesv's answer, I derived the following utility method (hope I got it right):
public static Mock<T> AnonymousMock<T>() where T : class
{
var mock = new Mock<T>();
fixture.Customize<T>(c => c.FromFactory(() => mock.Object));
fixture.CreateAnonymous<T>();
fixture.Customizations.RemoveAt(0);
return mock;
}
This is actually possible to do with AutoFixture, but it does require a bit of tweaking. The extensibility points are all there, but I admit that in this case, the solution isn't particularly discoverable.
It becomes even harder if you want it to work with nested/complex types.
Given the MyObject class above, as well as this MyParent class:
public class MyParent
{
public MyObject Object { get; set; }
public string Text { get; set; }
}
these unit tests all pass:
public class Scenario
{
[Fact]
public void CreateMyObject()
{
var fixture = new Fixture().Customize(new MockHybridCustomization());
var actual = fixture.CreateAnonymous<MyObject>();
Assert.NotNull(actual.A);
Assert.NotNull(actual.B);
Assert.NotNull(actual.C);
}
[Fact]
public void MyObjectIsMock()
{
var fixture = new Fixture().Customize(new MockHybridCustomization());
var actual = fixture.CreateAnonymous<MyObject>();
Assert.NotNull(Mock.Get(actual));
}
[Fact]
public void CreateMyParent()
{
var fixture = new Fixture().Customize(new MockHybridCustomization());
var actual = fixture.CreateAnonymous<MyParent>();
Assert.NotNull(actual.Object);
Assert.NotNull(actual.Text);
Assert.NotNull(Mock.Get(actual.Object));
}
[Fact]
public void MyParentIsMock()
{
var fixture = new Fixture().Customize(new MockHybridCustomization());
var actual = fixture.CreateAnonymous<MyParent>();
Assert.NotNull(Mock.Get(actual));
}
}
What's in MockHybridCustomization? This:
public class MockHybridCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(
new MockPostprocessor(
new MethodInvoker(
new MockConstructorQuery())));
fixture.Customizations.Add(
new Postprocessor(
new MockRelay(t =>
t == typeof(MyObject) || t == typeof(MyParent)),
new AutoExceptMoqPropertiesCommand().Execute,
new AnyTypeSpecification()));
}
}
The MockPostprocessor, MockConstructorQuery and MockRelay classes are defined in the AutoMoq extension to AutoFixture, so you'll need to add a reference to this library. However, note that it's not required to add the AutoMoqCustomization.
The AutoExceptMoqPropertiesCommand class is also custom-built for the occasion:
public class AutoExceptMoqPropertiesCommand : AutoPropertiesCommand<object>
{
public AutoExceptMoqPropertiesCommand()
: base(new NoInterceptorsSpecification())
{
}
protected override Type GetSpecimenType(object specimen)
{
return specimen.GetType();
}
private class NoInterceptorsSpecification : IRequestSpecification
{
public bool IsSatisfiedBy(object request)
{
var fi = request as FieldInfo;
if (fi != null)
{
if (fi.Name == "__interceptors")
return false;
}
return true;
}
}
}
This solution provides a general solution to the question. However, it hasn't been extensively tested, so I'd love to get feedback on it.
Probably there is a better why, but this works:
var fixture = new Fixture();
var moq = new Mock<MyObject>() { DefaultValue = DefaultValue.Mock };
moq.Setup(m => m.GetSomeValue()).Returns(3);
fixture.Customize<MyObject>(c => c.FromFactory(() => moq.Object));
var anyMyObject = fixture.CreateAnonymous<MyObject>();
Assert.AreEqual(3, anyMyObject.GetSomeValue());
Assert.IsNotNull(anyMyObject.A);
//...
Initially I tried to use fixture.Register(() => moq.Object); instead of fixture.Customize but it registers the creator function with OmitAutoProperties() so it wouldn't work for you case.
As of 3.20.0, you can use AutoConfiguredMoqCustomization. This will automatically configure all mocks so that their members' return values are generated by AutoFixture.
var fixture = new Fixture().Customize(new AutoConfiguredMoqCustomization());
var mock = fixture.Create<Mock<MyObject>>();
Assert.NotNull(mock.Object.A);
Assert.NotNull(mock.Object.B);
Assert.NotNull(mock.Object.C);

Unit-testing an action which calls session object

How can I unit test a method which uses a session object inside of its body?
Let us say I have the following action:
[HttpPost]
public JsonResult GetSearchResultGrid(JqGridParams gridParams, Guid campaignId, string queryItemsString)
{
var queryItems = new JavaScriptSerializer().Deserialize<IList<FilledQueryItem>>(queryItemsString);
IPageData pageData = gridParams.ToPageData();
var extraFieldLinker = SessionHandler.CurrentExtraFieldsLinker;
var searchParams = new SearchParamsModel(extraFieldLinker, queryItems);
IList<CustomerSearchResultRow> searchResults = null;
searchResults = _customerService.SearchCustomersByUrlAndCampaign(campaignId,
searchParams.SearchString,
searchParams.AddressFilterPredicate,
pageData);
return GetGridData<CustomerSearchResultGridDefinition, CustomerSearchResultRow>(searchResults, pageData);
}
I made the following unit tests which fails so far because of the session thing:
[Test]
public void CanGetSearchResultGrid()
{
//Initialize
var mockJqGridParams = new Mock<JqGridParams>();
var mockPageData = new Mock<IPageData>();
IPagedList<CustomerSearchResultRow> mockPagedResult = new PagedList<CustomerSearchResultRow>(mockPageData.Object);
var guid= Guid.NewGuid();
const string searchString =
"[{\"Caption\":\"FirstName\",\"ConditionType\":\"contains\",\"Value\":\"d\",\"NextItem\":\"Last\"}]";
Func<Address,bool> addressFilterPredicate = (x => true);
//Setup
mockJqGridParams.Setup(x => x.ToPageData()).Returns(mockPageData.Object);
_customerService.Setup(x => x.SearchCustomersByUrlAndCampaign(guid, searchString, addressFilterPredicate, mockPageData.Object))
.Returns(mockPagedResult);
//Call
var result = _homeController.GetSearchResultGrid(mockJqGridParams.Object, guid, searchString);
mockJqGridParams.Verify(x => x.ToPageData(), Times.Once());
_customerService.Verify(x => x.SearchCustomersByUrlAndCampaign(guid, searchString, addressFilterPredicate, mockPageData.Object)
, Times.Once());
//Verify
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.TypeOf(typeof(JsonResult)));
}
And the method from the helper of course:
public static ExtraFieldsLinker CurrentExtraFieldsLinker
{
get
{
object extraFieldLinker = GetSessionObject(EXTRA_FIELDS_LINKER);
return extraFieldLinker as ExtraFieldsLinker;
}
set { SetSessionObject(EXTRA_FIELDS_LINKER, value); }
}
I've solved similar issues (use of static data accessors that aren't mock friendly - in particular, HttpContext.Current) by wrapping the access in another object, and accessing it through an interface. You could do something like:
pubic interface ISessionData
{
ExtraFieldsLinker CurrentExtraFieldsLinker { get; set; }
}
public class SessionDataImpl : ISessionData
{
ExtraFieldsLinker CurrentExtraFieldsLinker
{
// Note: this code is somewhat bogus,
// since I think these are methods of your class.
// But it illustrates the point. You'd put all the access here
get { return (ExtraFieldsLinker)GetSessionObject(EXTRA_FIELDS_LINKER); }
set { SetSessionObject(EXTRA_FIELDS_LINKER, value); }
}
}
public class ClassThatContainsYourAction
{
static ClassThatContainsYourAction()
{
SessionData = new SessionDataImpl();
}
public static ISessionData SessionData { get; private set; }
// Making this access very ugly so you don't do it by accident
public void SetSessionDataForUnitTests(ISessionData sessionData)
{
SessionData = sessionData;
}
[HttpPost]
public JsonResult GetSearchResultGrid(JqGridParams gridParams,
Guid campaignId, string queryItemsString)
{
var queryItems = // ...
IPageData pageData = // ...
// Access your shared state only through SessionData
var extraFieldLinker = SessionData.CurrentExtraFieldsLinker;
// ...
}
}
Then your unit test can set the ISessionData instance to a mock object before calling GetSearchResultGrid.
Ideally you'd use a Dependency Injection library at some point, and get rid of the static constructor.
If you can figure out a way to make your ISessionData an instanced object instead of static, even better. Mock object frameworks tend to like to create a new mock type for every test case, and having mocks lying around from previous tests is kind of gross. I believe session state is going to be global to your session anyway, so you might not have to do anything tricky to make a non-static object work.

Categories

Resources