EF Core Unit Tests
- 12 minutes to read
You can use Moq and xUnit to write lightweight unit tests for XAF Controllers, Actions, and other custom UI logic. Unlike EasyTest, this approach does not require a running XAF application instance and allows you to test isolated components.
This topic lists ways to test different parts of an XAF application.
- Test Action state based on user permissions
- Test whether an Action changes property values
- Test event handlers in Controllers
- Test Action state based on target criteria in Detail View
- Test Action state based on selection dependency in List View
- Test New Action Custom Business Logic (New Object Inherits Parent Property Values)
- Test object queries by criteria and Detail View creation
- Test localized strings from CaptionHelper
- Web API Service Integration Tests
Tip
You can find the test examples described in this section in the following GitHub repositories:
- EF Core unit tests: XAFUnitTestEFCore
- Web API Service integration tests: MainDemo.WebAPI.Tests
Test Action State Based on User Permissions
Test Scenario
The TaskActionsControllerenables/disables the SetTaskAction depending on user permissions.
A test must ensure that the UpdateSetTaskActionState method correctly enables or disables SetTaskAction based on the user’s Write permissions for the Status and Priority properties of selected DemoTask objects. If the user cannot write either of these properties, the action is disabled.
public void UpdateSetTaskActionState() {
bool isGranted = true;
foreach(object selectedObject in View.SelectedObjects) {
bool isPriorityGranted = SecuritySystem.IsGranted(new PermissionRequest(ObjectSpace, typeof(DemoTask), SecurityOperations.Write, selectedObject, nameof(DemoTask.Priority)));
bool isStatusGranted = SecuritySystem.IsGranted(new PermissionRequest(ObjectSpace, typeof(DemoTask), SecurityOperations.Write, selectedObject, nameof(DemoTask.Status)));
if(!isPriorityGranted || !isStatusGranted) {
isGranted = false;
}
}
SetTaskAction.Enabled.SetItemValue("SecurityAllowance", isGranted);
}
Unit Test Implementation
The following code snippet verifies that the SetTaskAction is enabled or disabled based on security permissions for selected objects:
[Theory]
[InlineData(new[] { "Granted", "Denied" }, false)]
[InlineData(new[] { "Granted", "Granted" }, true)]
public void TaskTest(string[] selectedObjects, bool allowed) {
using TaskActionsController controller = new TaskActionsController();
controller.SetView(PrepareView(selectedObjects));
controller.UpdateSetTaskActionState();
Assert.Equal(allowed, controller.SetTaskAction.Enabled["SecurityAllowance"]);
}
GitHub Files to review:
Test Whether an Action Changes Property Values
Test Scenario
The TaskActionsController implements the SetTaskAction that allows users to change the Priority and Status property values of the selected object.
A test must check that the Action changes the Priority property and commits the result.
public SingleChoiceAction SetTaskAction;
public TaskActionsController() {
TargetObjectType = typeof(DemoTask);
SetTaskAction = new SingleChoiceAction(this, "SetTaskAction", PredefinedCategory.Edit);
setPriorityItem = new ChoiceActionItem(CaptionHelper.GetMemberCaption(typeof(DemoTask), nameof(DemoTask.Priority)), null);
SetTaskAction.Items.Add(setPriorityItem);
FillItemWithEnumValues(setPriorityItem, typeof(Priority));
setStatusItem = new ChoiceActionItem(CaptionHelper.GetMemberCaption(typeof(DemoTask), nameof(DemoTask.Status)), null);
SetTaskAction.Items.Add(setStatusItem);
FillItemWithEnumValues(setStatusItem, typeof(TaskStatus));
SetTaskAction.Execute += SetTaskAction_Execute;
}
// ...
private void SetTaskAction_Execute(object sender, SingleChoiceActionExecuteEventArgs args) {
bool viewIsListview = View is ListView;
IObjectSpace objectSpace = viewIsListview
? Application.CreateObjectSpace(typeof(DemoTask))
: View.ObjectSpace;
SetupDemoTaskProperties(args.SelectedObjects, args.SelectedChoiceActionItem, objectSpace, viewIsListview);
if(viewIsListview) {
View.ObjectSpace.Refresh();
}
}
// ...
public void SetupDemoTaskProperties(IList selectedObjects, ChoiceActionItem selectedChoiceActionItem, IObjectSpace objectSpace, bool shouldCommitChanges) {
foreach(object obj in selectedObjects) {
if(objectSpace.GetObject(obj) is DemoTask objInNewObjectSpace) {
if(selectedChoiceActionItem.ParentItem == setPriorityItem) {
objInNewObjectSpace.Priority = (Priority)selectedChoiceActionItem.Data;
} else if(selectedChoiceActionItem.ParentItem == setStatusItem) {
objInNewObjectSpace.Status = (TaskStatus)selectedChoiceActionItem.Data;
}
}
}
if(shouldCommitChanges) {
objectSpace.CommitChanges();
}
}
Unit Test Implementation
The following code snippet tests whether SetTaskAction correctly sets the Priority property for selected DemoTask objects and commits the changes:
[Fact]
public void SetTaskActionSetPriorityTest() {
DemoTask[] demoTasks = {
new DemoTask(){Priority=Priority.Low},
new DemoTask(){Priority=Priority.Normal}
};
using TaskActionsController controller = new TaskActionsController();
const Priority priority = Priority.High;
var selectedChoiceActionItem = controller.setPriorityItem.Items.Find(priority);
var objectSpaceMock = new Mock<IObjectSpace>();
objectSpaceMock.Setup(o => o.GetObject(It.IsAny<object>())).Returns<object>(obj => obj);
controller.SetupDemoTaskProperties(demoTasks, selectedChoiceActionItem, objectSpaceMock.Object, true);
Assert.All(demoTasks, dt => Assert.Equal(priority, dt.Priority));
objectSpaceMock.Verify(o => o.CommitChanges(), Times.Once);
}
GitHub Files to review:
Test Event Handlers in Controllers
Test Scenario
The CreateLinkedSaleBaseDescendantController subscribes to the NewObjectViewController.ObjectCreated event.
A test must check that NewObjectViewController exists and that the correct handler is subscribed to the ObjectCreated event.
public class CreateLinkedSaleBaseDescendantController :ViewController {
protected override void OnActivated() {
base.OnActivated();
newObjectController = Frame.GetController<NewObjectViewController>();
newObjectController.ObjectCreated += CreateLinkedSaleBaseDescendantController_ObjectCreated;
}
NewObjectViewController newObjectController;
public void CreateLinkedSaleBaseDescendantController_ObjectCreated(object sender, ObjectCreatedEventArgs e) {
SaleBase createdObject = e.CreatedObject as SaleBase;
IObjectSpace objectSpace = e.ObjectSpace;
CreateLinkedSaleBase(objectSpace, createdObject);
}
public void CreateLinkedSaleBase(IObjectSpace objectSpace, SaleBase createdObject) {
NestedFrame nestedFrame = Frame as NestedFrame;
if(nestedFrame != null) {
SaleBase parentObject = objectSpace.GetObject(nestedFrame.ViewItem.CurrentObject as SaleBase);
if(createdObject != null) {
parentObject?.Copy(createdObject);
}
}
}
// ...
}
Unit Test Implementation
The following code snippet verifies that the CreateLinkedSaleBaseDescendantController correctly subscribes its CreateLinkedSaleBaseDescendantController_ObjectCreated handler to the NewObjectViewController.ObjectCreated event.
[Fact]
public void ControllerEventSubscriptionTest() {
using NewObjectViewController newObjectViewController = new NewObjectViewController();
using CreateLinkedSaleBaseDescendantController controller = new CreateLinkedSaleBaseDescendantController();
using Frame frame = new Frame(null, null);
frame.RegisterController(newObjectViewController);
frame.RegisterController(controller);
controller.Active.Clear();
controller.Active["Test"] = true;
string targetMethodName = nameof(CreateLinkedSaleBaseDescendantController.CreateLinkedSaleBaseDescendantController_ObjectCreated);
VerifyEventSubscription(
newObjectViewController,
nameof(newObjectViewController.ObjectCreated),
controller,
$"Void {targetMethodName}({typeof(object)}, {typeof(ObjectCreatedEventArgs)})"
);
}
static void VerifyEventSubscription(object objectWithEvent, string eventName, object subscriber, string methodSignature) {
var allBindings = BindingFlags.IgnoreCase | BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
var type = objectWithEvent.GetType();
var fieldInfo = type.GetField(eventName, allBindings);
Assert.NotNull(fieldInfo);
var eventHandler = fieldInfo.GetValue(objectWithEvent) as Delegate;
Assert.NotNull(eventHandler);
Assert.Equal(subscriber, eventHandler.Target);
Assert.Equal(methodSignature, eventHandler.Method.ToString());
}
GitHub Files to review:
Test Action State Based on Target Criteria in Detail View
Test Scenario
A Controller enables a Detail View Action if the current object matches the Action’s target criteria.
A test must ensure that the Detail View Action is enabled only when the current object matches the action’s target criteria.
Unit Test Implementation
The following code snippet verifies that XAF correctly evaluates a SimpleAction’s TargetObjectsCriteria in a Detail View. It checks that the action configured for objects with Priority = 2 is enabled for a high-priority DemoTask and disabled for a low-priority DemoTask.
[Theory]
[InlineData(Priority.High, true)]
[InlineData(Priority.Low, false)]
public void ActionsInDetailViewTest(Priority priority, bool result) {
using ActionsCriteriaViewController actionsCriteriaViewController = new ActionsCriteriaViewController();
using ViewController testedController = new ViewController();
SimpleAction testedAction = new SimpleAction(testedController, "High Priority", string.Empty);
testedAction.TargetObjectsCriteria = "Priority = 2";
DemoTask task = new DemoTask();
task.Priority = priority;
var objectSpaceMock = new Mock<IObjectSpace>();
objectSpaceMock.Setup(os => os.Contains(It.IsAny<object>())).Returns(true);
objectSpaceMock.Setup(os => os.GetEvaluatorContextDescriptor(typeof(DemoTask))).Returns(new EvaluatorContextDescriptorDefault(typeof(DemoTask)));
objectSpaceMock.Setup(os => os.GetObject(It.IsAny<object>())).Returns<object>(obj => obj);
ITypesInfo info = new TypesInfo();
info.LoadTypes(Assembly.GetAssembly(typeof(DemoTask)));
objectSpaceMock.Setup(os => os.TypesInfo).Returns(info);
var applicationMock = new Mock<XafApplication>();
using DetailView detailView = new DetailView(objectSpaceMock.Object, task, applicationMock.Object, true);
using Frame frame = new Frame(applicationMock.Object, TemplateContext.View, actionsCriteriaViewController, testedController);
frame.SetView(detailView);
Assert.Equal(result, testedAction.Enabled.ResultValue);
}
GitHub File to review:
Test Action State Based on Selection Dependency in List View
Test Scenario
A Controller enables an Action if records selected in the List View meet particular criteria.
A test must ensure that the List View Action is enabled or disabled correctly based on selected objects, according to the Action’s selection dependency and target criteria evaluation mode.
Unit Test Implementation
The following code snippet tests whether an XAF SimpleAction is enabled in a List View depending on:
- The Action’s
SelectionDependencyType - The Action’s
TargetObjectsCriteriaMode - The current selected objects
[Theory]
[InlineData(SelectionDependencyType.Independent, TargetObjectsCriteriaMode.TrueAtLeastForOne, true)]
[InlineData(SelectionDependencyType.Independent, TargetObjectsCriteriaMode.TrueForAll, false)]
[InlineData(SelectionDependencyType.RequireSingleObject, TargetObjectsCriteriaMode.TrueAtLeastForOne, false)]
[InlineData(SelectionDependencyType.RequireMultipleObjects, TargetObjectsCriteriaMode.TrueAtLeastForOne, true)]
[InlineData(SelectionDependencyType.RequireMultipleObjects, TargetObjectsCriteriaMode.TrueForAll, false)]
public void ActionsInListViewTest(SelectionDependencyType dependencyType, TargetObjectsCriteriaMode criteriaMode, bool result) {
using ViewController testedController = new ViewController();
SimpleAction testedAction = new SimpleAction(testedController, "High Priority", string.Empty);
testedAction.TargetObjectsCriteria = "Priority = 2";
testedAction.SelectionDependencyType = dependencyType;
testedAction.TargetObjectsCriteriaMode = criteriaMode;
List<DemoTask> tasks = new List<DemoTask> {
new DemoTask() {Priority = Priority.High},
new DemoTask() {Priority = Priority.Low}
};
using ActionsCriteriaViewController actionsCriteriaViewController = new ActionsCriteriaViewController();
var applicationMock = new Mock<XafApplication>();
var listEditorMock = new Mock<ListEditor>();
listEditorMock.Setup(e => e.GetSelectedObjects()).Returns(tasks);
listEditorMock.Setup(e => e.SupportsDataAccessMode(It.IsAny<CollectionSourceDataAccessMode>())).Returns(true);
listEditorMock.Setup(e => e.SelectionType).Returns(SelectionType.Full);
var objectSpaceMock = new Mock<IObjectSpace>();
objectSpaceMock.Setup(os => os.GetEvaluatorContextDescriptor(typeof(DemoTask))).Returns(new EvaluatorContextDescriptorDefault(typeof(DemoTask)));
ITypesInfo info = new TypesInfo();
info.LoadTypes(Assembly.GetAssembly(typeof(DemoTask)));
objectSpaceMock.Setup(os => os.TypesInfo).Returns(info);
using ListView listView = new ListView(new CollectionSource(objectSpaceMock.Object, typeof(DemoTask)), listEditorMock.Object);
using Frame frame = new Frame(applicationMock.Object, TemplateContext.View, actionsCriteriaViewController, testedController);
frame.SetView(listView);
Assert.Equal(result, testedAction.Enabled.ResultValue);
}
GitHub File to review:
Test New Action Custom Business Logic (New Object Inherits Parent Property Values)
Test Scenario
The CreateLinkedSaleBaseDescendantController copies properties of a current object to a new object.
A test must ensure that the CreateLinkedSaleBase method correctly copies the parent object’s properties to the newly created linked object.
public class CreateLinkedSaleBaseDescendantController :ViewController {
protected override void OnActivated() {
base.OnActivated();
newObjectController = Frame.GetController<NewObjectViewController>();
newObjectController.ObjectCreated += CreateLinkedSaleBaseDescendantController_ObjectCreated;
}
NewObjectViewController newObjectController;
public void CreateLinkedSaleBaseDescendantController_ObjectCreated(object sender, ObjectCreatedEventArgs e) {
SaleBase createdObject = e.CreatedObject as SaleBase;
IObjectSpace objectSpace = e.ObjectSpace;
CreateLinkedSaleBase(objectSpace, createdObject);
}
public void CreateLinkedSaleBase(IObjectSpace objectSpace, SaleBase createdObject) {
NestedFrame nestedFrame = Frame as NestedFrame;
if(nestedFrame != null) {
SaleBase parentObject = objectSpace.GetObject(nestedFrame.ViewItem.CurrentObject as SaleBase);
if(createdObject != null) {
parentObject?.Copy(createdObject);
}
}
}
// ...
}
Unit Test Implementation
The following code snippet tests that the CreateLinkedSaleBase method copies relevant property values from the current parent SaleBase object to the newly created linked SaleBase object.
[Fact]
public void TestObjectPropertiesCopy() {
Mock<IObjectSpace> objectSpaceMock = new Mock<IObjectSpace>();
objectSpaceMock.Setup(m => m.CreateObject<SaleBase>()).Returns(() => {
var objMock = new Mock<SaleBase>();
objMock.SetupAllProperties();
objMock.Object.SaleItems = new List<SaleItem>();
return objMock.Object;
});
var createdObject = objectSpaceMock.Object.CreateObject<SaleBase>();
var parentObject = objectSpaceMock.Object.CreateObject<SaleBase>();
parentObject.Discount = 123m;
objectSpaceMock.Setup(m => m.GetObject(It.Is<SaleBase>(s => s == parentObject))).Returns(parentObject);
ITypesInfo info = new TypesInfo();
info.LoadTypes(Assembly.GetAssembly(typeof(SaleItem)));
objectSpaceMock.Setup(os => os.TypesInfo).Returns(info);
Mock<ViewItem> viewItemMock = new Mock<ViewItem>(typeof(SaleBase), "testID");
viewItemMock.Object.CurrentObject = parentObject;
using NestedFrame nestedFrame = new NestedFrame(null, null, viewItemMock.Object, new List<Controller>());
using CreateLinkedSaleBaseDescendantController controller = new CreateLinkedSaleBaseDescendantController();
nestedFrame.RegisterController(controller);
controller.CreateLinkedSaleBase(objectSpaceMock.Object, createdObject);
Assert.Equal(123, createdObject.Discount);
}
GitHub Files to review:
Test Object Queries by Criteria and Detail View Creation
Test Scenario
The FindBySubjectController finds an object by a criterion and opens its Detail View on an Action click.
A test must check that the controller can find a DemoTask by a subject substring and create a Detail View for the found object.
public class FindBySubjectController :ViewController {
public FindBySubjectController() {
TargetObjectType = typeof(DemoTask);
ParametrizedAction findBySubjectAction =
new ParametrizedAction(this, "FindBySubjectAction", PredefinedCategory.View, typeof(string)) {
ImageName = "Action_Search",
NullValuePrompt = "Find task by subject…"
};
findBySubjectAction.Execute += FindBySubjectAction_Execute;
}
private void FindBySubjectAction_Execute(object sender, ParametrizedActionExecuteEventArgs e) {
string paramValue = e.ParameterCurrentValue as string;
var createdView = CreateObjectViewBySubject(View.ObjectTypeInfo.Type, paramValue as string);
e.ShowViewParameters.CreatedView = createdView;
}
public View CreateObjectViewBySubject(Type objectType, string subject) {
CriteriaOperator criteria = CriteriaOperator.FromLambda<DemoTask>(t => t.Subject.Contains(subject));
IObjectSpace objectSpace = Application.CreateObjectSpace(objectType);
object obj = objectSpace.FindObject(objectType, criteria);
DetailView createdView = null;
if(obj != null) {
createdView = Application.CreateDetailView(objectSpace, obj);
}
return createdView;
}
}
Unit Test Implementation
The following code snippet tests that the Controller finds a DemoTask by subject text and opens it in a Detail View.
[Fact]
public void FindSubjectTest() {
var objectType = typeof(DemoTask);
const string targetSubject = "Some subject";
DemoTask targetDemoTask = new DemoTask() { Subject = "Some subject 3" };
var objectSpaceMock = new Mock<IObjectSpace>();
objectSpaceMock.Setup(o => o.FindObject(
It.Is<Type>(t => t == objectType),
It.Is<CriteriaOperator>(c => c.ToString() == CriteriaOperator.FromLambda<DemoTask>(t => t.Subject.Contains(targetSubject)).ToString())
))
.Returns(targetDemoTask);
objectSpaceMock.Setup(o => o.Contains(
It.Is<DemoTask>(dt => ReferenceEquals(dt, targetDemoTask))
))
.Returns(true);
ITypesInfo info = new TypesInfo();
info.LoadTypes(Assembly.GetAssembly(typeof(DemoTask)));
objectSpaceMock.Setup(os => os.TypesInfo).Returns(info);
var typeInfoMock = new Mock<ITypesInfo>();
var applicationMock = new Mock<XafApplication>(typeInfoMock.Object);
applicationMock.Protected()
.Setup<IObjectSpace>(
"CreateObjectSpaceCore",
ItExpr.Is<Type>(t => t == objectType)
)
.Returns(objectSpaceMock.Object);
applicationMock.Protected()
.Setup<bool>("IsCompatibilityChecked")
.Returns(true);
using DetailView expectedDetailView = new DetailView(objectSpaceMock.Object, targetDemoTask, applicationMock.Object, true);
applicationMock.Setup(a => a.CreateDetailView(
It.Is<IObjectSpace>(o => o == objectSpaceMock.Object),
"",
true,
It.Is<object>(o => ReferenceEquals(o, targetDemoTask)),
false,
null
))
.Returns(expectedDetailView);
using FindBySubjectController controller = new FindBySubjectController();
controller.Application = applicationMock.Object;
var currentDetailView = controller.CreateObjectViewBySubject(objectType, targetSubject);
applicationMock.Verify();
Assert.Equal(expectedDetailView, currentDetailView);
}
GitHub Files to review:
Test Localized Strings from CaptionHelper
Test Scenario
The Application Model contains localized strings. A test must ensure that the localized strings are correctly applied.
<Localization>
<LocalizationGroup Name="Messages">
<LocalizationItem Name="TestString" Value="Test" IsNewNode="True" />
</LocalizationGroup>
</Localization>
Unit Test Implementation. Approach 1
The following code snippet verifies that the CaptionHelper.GetLocalizedText method correctly retrieves a localized string ("Test") from the Application Model by manually constructing the model layer.
[Fact]
public void GetLocalizedTextTest_ModelApplicationWay() {
XAFUnitTestEFCoreModule module = new XAFUnitTestEFCoreModule();
ITypesInfo info = new TypesInfo();
info.LoadTypes(Assembly.GetAssembly(typeof(XAFUnitTestEFCoreEFCoreDbContext)));
ModelApplicationCreatorProperties properties = ModelApplicationCreatorProperties.CreateDefault(info);
ModelApplicationCreator modelApplicationCreator = ModelApplicationCreator.GetModelApplicationCreator(properties);
ModelApplicationBase modelApplicationBase = modelApplicationCreator.CreateModelApplication();
module.DiffsStore.Load(modelApplicationBase);
IModelApplication modelApplication = (IModelApplication)modelApplicationBase;
CaptionHelper.Setup(modelApplication);
Assert.Equal("Test", CaptionHelper.GetLocalizedText("Messages", "TestString"));
}
Unit Test Implementation. Approach 2
The following code snippet verifies that the CaptionHelper.GetLocalizedText method correctly retrieves a localized string ("Test") from the Application Model after the application is initialized via ExpressApplicationSetupParameters.
[Fact]
public void GetLocalizedTextTest_ExpressApplicationSetupParametersWay() {
var applicationMock = new Mock<XafApplication>();
applicationMock.CallBase = true;
ModuleList moduleList = new ModuleList {
new XAFUnitTestEFCoreModule()
};
var entityStoreMock = new Mock<IEntityStore>();
var objectSpaceProviderMock = new Mock<IObjectSpaceProvider>();
objectSpaceProviderMock.Setup(o => o.EntityStore).Returns(entityStoreMock.Object);
ExpressApplicationSetupParameters setupParameters = new ExpressApplicationSetupParameters(null, objectSpaceProviderMock.Object, new ControllersManager(), moduleList);
applicationMock.Object.Setup(setupParameters);
Assert.Equal("Test", CaptionHelper.GetLocalizedText("Messages", "TestString"));
}
GitHub Files to review:
Web API Service Integration Tests
For end-to-end scenarios that test CRUD operations, authentication, and business object Actions through a hosted Web API service, use the Microsoft.AspNetCore.TestHost infrastructure. You can find a number of test examples in our MainDemo.NET.EFCore demo application, in the MainDemo.E2E.Tests project. Some of the tests are listed below:
- Check an Unauthorized Access
- The test verifies that unauthenticated requests return a
401 Unauthorizedstatus code. - Get Business Objects
- The test authenticates and retrieves business objects from the Web API.
- Create and Delete a Business Object
- The test creates an
ApplicationUser, verifies the creation, then deletes it. - Get a Business Object with a Reference Property
- The test retrieves an
ApplicationUserwith itsUserLoginsreference expanded. - Assign an Object to a Reference Property
- The test creates an
Employee, assigns aDepartmentto it via the$refendpoint, and verifies the link. - Add an Object to a Collection
- The test creates a
DemoTask, adds it to an Employee’sTaskscollection via the$refendpoint, and verifies the association. - Deep Create (Nested Objects in POST)
- The test creates an
Employeewith nestedTasksin a single POST request. - Deep Update (Create a Reference Object via PATCH)
- The test creates an
Employee, then uses a PATCH request to create a nestedDepartmentwith positions. - Invoke a Business Object Action
- The test creates a
DemoTask, invokes thePostponeAction endpoint, and verifies that theDueDatehas been postponed. - Test Validation
- The test verifies that the Web API returns a validation error when required properties are missing.
- Test Localization Endpoints
- The test retrieves localized captions from the Localization endpoint with different
Accept-Languageheaders.