Testing your code with .NET Adapter
With the new version of the .NET Adapter, it is now much possible to write unit tests that mock various aspects of the adapter.
The changes that have taken place to enable this include:
- Builder-style runtime creation
- A mockable type resolution service
- The returning of interfaces from creation methods rather than concrete types
An Example
This example assume you have a simple model class that reacts to interop context changes by setting a property with a field from the context payload.
Ideally you would want to unit test this in isolation of OpenFin by passing in a variety of scenarios to the context receiver and asserting that the model handles these as expected.
The code to be tested
For the purposes of this example, the model class looks like the following.
internal class MainWindowVM
{
private readonly IRuntime runtime;
private IChannelClient channelClient;
public string CurrentId { private set; get; }
// Now we pass in the runtime interface when we create the model
public MainWindowVM(IRuntime runtime)
{
this.runtime = runtime;
// Connect to the broker and add a handler for a context change
var interop = runtime.GetService<IInterop>();
interopClient = await interop.ConnectAsync("the-broker");
await interopClient.AddContextHandlerAsync(HandleContext, "my-context");
}
private void HandleContext(Context context)
{
if (context.Type == "fdc3.contact")
{
if ((context.Id as JsonObject).TryGetPropertyValue("email", out var valueNode))
{
CurrentId = valueNode.ToString();
}
}
}
}
The Tests
Now we will create a simple test that checks that the CurrentId property within the model is updated only on an fdc3 contact context change.
Note
For mocking we make use of the popular MOQ library. You may use any mocking library that you prefer such as Rhino Mocks.
[TestClass]
public class InteropTests
{
[TestMethod]
public void TestCurrentIdIsCorrectlySetAfterAContextChange()
{
// First we need to setup our mocks...
ContextHandler contextHandler = null;
// Create a Mock for IRuntime
var runtimeMock = new Mock<IRuntime>();
// Create a mock for the entire IInterop api
var interopMock = new Mock<IInterop>();
// Setup the IRuntime so that when the model asks for IInterop we return our own mock
runtimeMock.Setup(x => x.GetService<IInterop>())
.Returns(interopMock.Object);
// Create a mock for the entire IInteropClient api
var interopClientMock = new Mock<IInteropClient>();
// Now create a setup on the IInterop mock for when the model connects to the broker
// We return our previously create IInteropClient Mock
interopMock.Setup(x => x.ConnectAsync("the-broker"))
.ReturnsAsync( interopClientMock.Object );
// When the model adds a handler for a context change, we want out test to intercept that and store
// the handler so we can invoke it directly later
interopClientMock.Setup(x => x.AddContextHandlerAsync(It.IsAny<ContextHandler>(), "my-context"))
.Callback((ContextHandler handler, string contextType) =>
{
contextHandler = handler;
});
// Now we can test the model...
// Create our model and give it the runtime mock
// This will in turn connect to the broker and invoke the various setups above
var vm = new MainWindowVM(runtimeMock.Object);
// Should have the context handler now ready for us to invoke directly with a dummy context
Assert.IsNotNull(contextHandler, "AddContextHandler should have been called by this point");
// Set an initial context
contextHandler(new Context
{
Type = "fdc3.contact",
Id = new JsonObject
{
{ "email", "john.mchugh@gmail.com" }
}
});
Assert.AreEqual("john.mchugh@gmail.com", vm.CurrentId);
// Change it
contextHandler(new Context
{
Type = "fdc3.contact",
Id = new JsonObject
{
{ "email", "mary.rose@hotmail.com" }
}
});
// Should have changed
Assert.AreEqual("mary.rose@hotmail.com", vm.CurrentId);
// Fire a context that is a different context type
contextHandler(new Context
{
Type = "fdc3.instrument",
Id = new JsonObject
{
{ "isin", "abcdefg123" }
}
});
// Should have ignored this update
Assert.AreEqual("mary.rose@hotmail.com", vm.CurrentId);
}
}
Conclusion
Using the above example, you should now be able to invoke your own code using mock .NET adapter objects without any need to write your own wrapper around the adapter or running an actual OpenFin runtime during tests.