I have the following scenario that I have not come across so far using Ninject. I have the following class structure (simplified for easy of reading :]). First the abstract base class for all IDocument
s
public abstract class DocumentController : IDocumentController, IDisposable
{
...
private IMessageBoxService messageBoxService;
private IUndoRedoManager undoRedoManager;
private FileSystemObserver observer;
private bool fileChanging;
public DocumentController(IMessageBoxService messageBoxService)
{
if (messageBoxService == null)
throw new ArgumentNullException("messageBoxService");
this.messageBoxService = messageBoxService;
}
...
private void FileChangedHandler(string p)
{
if (!fileChanging && p.CompareNoCase(FilePath))
{
fileChanging = true;
try
{
(View as Form).UIThread(() =>
{
DialogResult result = messageBoxService.DisplayMessage(
(Form)View,
String.Format(
MessageStrings.DocumentController_DocumentChanged,
FilePath,
Constants.Trademark),
CaptionStrings.ChangeNotification,
MessageBoxButtons.YesNo, MessageBoxIcon.Information);
if (result == DialogResult.Yes)
{
if (TryClose(new FormClosingEventArgs(CloseReason.None, false)))
{
MediatorService.Instance.NotifyColleagues(
MediatorService.Messages.OpenDocuments,
new List<string>() { FilePath });
Dispose();
}
}
});
}
finally
{
fileChanging = false;
}
}
}
// Omitted code for not saved prompt for brevity.
}
This class watches all documents that are open and if they are changed in the file system shows a prompt. If a close is attempted whilst they are in a "dirty" state, the same IMessageBoxService
prompts to save.
An example of the derived concrete class ctors is
public class TextEditorController : DocumentController, IZoom
{
...
public TextEditorController(ITextEditorView view, IMessageBoxService messageBoxService)
: base(messageBoxService)
{
if (view == null)
throw new ArgumentNullException("view");
this.view = view;
InitializeEventHandlers();
}
...
}
Now this works great when using the application. The problem I have is with unit testing. What I want to do is write a unit test that opens my text documents, makes some changes and then trys to close them when they are "dirty"/unsaved. This will show a message box, but I want to Mock the IMessageBoxService
and test whether the message box is indeed displayed under the right conditions. I have the following unit test class in which I have reverted to instantiating concrete implementations of things; it works but feels bad...
[TestClass]
public class DocumentManagementTests
{
private Mock<IProgress<string>> mockProgress;
private Mock<IMessageBoxService> mockMessageBoxService;
private int messageBoxInvocationCounter = 0;
private List<string> filePaths = new List<string>();
[TestInitialize]
public void Initialize()
{
mockProgress = new Mock<IProgress<string>>();
mockProgress.Setup(m => m.Report(It.IsAny<string>()))
.Callback((string s) =>
{
Trace.WriteLine(s);
});
Assert.IsNotNull(mockProgress);
// Load the file paths.
LoadTextDocumentFilePaths();
}
private void LoadTextDocumentFilePaths()
{
string path = String.Empty;
string directory = Utils.GetAssemblyDirectory();
foreach (var fileName in new List<string>() { "DocumentA.txt", "DocumentB.txt" })
{
path = Path.Combine(directory, "Resources", "TextEditorDocs", fileName);
if (!File.Exists(path))
throw new IOException($"{path} does not exist");
filePaths.Add(path);
}
Assert.AreEqual(2, filePaths.Count);
}
[TestMethod]
public void DocumentChangedSavePrompt_OnUserClosing_Test()
{
// Message box service mock.
mockMessageBoxService = new Mock<IMessageBoxService>();
mockMessageBoxService
.Setup(m => m.DisplayMessage(It.IsAny<IWin32Window>(), It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<MessageBoxButtons>(), It.IsAny<MessageBoxIcon>(), It.IsAny<MessageBoxDefaultButton>()))
.Returns((IWin32Window owner, string text,
string caption, MessageBoxButtons buttons, MessageBoxIcon icon,
MessageBoxDefaultButton defaultButton) => DialogResult.Cancel)
.Callback((IWin32Window owner, string text,
string caption, MessageBoxButtons buttons, MessageBoxIcon icon,
MessageBoxDefaultButton defaultButton) =>
{
Trace.WriteLine("MockMessageBoxService Invoked");
messageBoxInvocationCounter++;
});
Assert.IsNotNull(mockMessageBoxService);
// Open the documents.
List<TextEditorController> controllers = new List<TextEditorController>();
foreach (var path in filePaths)
{
// CONCRETE IMPLEMENTATIONS - BAD!??
TextEditorController controller = new TextEditorController(
CompositionRoot.Resolve<ITextEditorView>(),
mockMessageBoxService.Object);
Assert.IsNotNull(controller);
controllers.Add(controller);
TextEditorView view = (TextEditorView)controller.Open(path);
view.TextEditor.Text += "*";
Assert.IsTrue(controller.IsDirty);
}
// Test they are dirty and the message box is displayed.
foreach (var controller in controllers)
{
bool didClose = controller.TryClose(
new FormClosingEventArgs(CloseReason.UserClosing, false));
Assert.IsFalse(didClose);
}
Assert.AreEqual(2, messageBoxInvocationCounter);
}
}
I want to do something like this
[TestClass]
public class DocumentManagementTests
{
private IDocumentProvider documentProvider;
private Moq.Mock<IProgress<string>> mockProgress;
private List<string> filePaths = new List<string>();
#region Initialization.
[TestInitialize]
public void Initialize()
{
documentProvider = CompositionRoot.Resolve<IDocumentProvider>();
if (documentProvider == null)
throw new ArgumentNullException("documentManager");
mockProgress = new Mock<IProgress<string>>();
mockProgress.Setup(m => m.Report(It.IsAny<string>()))
.Callback((string s) =>
{
Trace.WriteLine(s);
});
LoadTextDocumentFilePaths();
}
private void LoadTextDocumentFilePaths()
{
// Add two real file paths to filePaths.
Assert.AreEqual(2, filePaths.Count);
}
[TestMethod]
public void DocumentChangedSavePromptTest()
{
// HOW DO I INJECT A MOCKED IMessageBoxService to `IDocumentController`?
// Open the documents internally.
var documents = documentProvider.GetDocumentViews(filePaths, mockProgress.Object);
Assert.AreEqual(2, documentProvider.DocumentControllerCache
.Count(d => d.GetType() == typeof(TextEditorController)));
// Ammend the text internally.
foreach (var d in documentProvider.DocumentControllerCache)
{
var controller = d.Key as TextEditorController;
}
// Need to finish writing the test.
}
}
So I want to know:
- How I can inject a mocked
IMessageBoxService
into the ctor for theIDocumentController
class? - Is the above is not possible, is there a way I can use DI in my test, if so how?
- Is what I have done reasonable in the worst case scenario?
Thanks for your time.