Skip to content

Commit 355d459

Browse files
committed
Add unit tests, replace tests targeting old interface.
1 parent ff75888 commit 355d459

File tree

9 files changed

+1001
-718
lines changed

9 files changed

+1001
-718
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
using System.Globalization;
3+
using System.Windows;
4+
using System.Windows.Data;
5+
6+
namespace Rubberduck.UI.Converters
7+
{
8+
public class NonZeroToBooleanConverter : IValueConverter
9+
{
10+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
11+
{
12+
if (!(value is int number))
13+
{
14+
return false;
15+
}
16+
17+
return number != 0 ? Visibility.Visible : Visibility.Hidden;
18+
}
19+
20+
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
21+
{
22+
throw new NotImplementedException();
23+
}
24+
}
25+
}

Rubberduck.Core/UI/UnitTesting/TestExplorerViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
namespace Rubberduck.UI.UnitTesting
2121
{
22-
internal enum TestExplorerGrouping
22+
public enum TestExplorerGrouping
2323
{
2424
None,
2525
Outcome,

Rubberduck.UnitTesting/UnitTesting/TestResult.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Rubberduck.UnitTesting
44
{
5-
public class TestResult
5+
public struct TestResult
66
{
77
public TestResult(TestOutcome outcome, string output = "", long duration = 0)
88
{

RubberduckTests/UnitTesting/EngineTests.cs

Lines changed: 164 additions & 291 deletions
Large diffs are not rendered by default.
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Linq;
5+
using Moq;
6+
using NUnit.Framework;
7+
using Rubberduck.Parsing.UIContext;
8+
using Rubberduck.Parsing.VBA;
9+
using Rubberduck.UnitTesting;
10+
using Rubberduck.VBEditor.ComManagement.TypeLibs;
11+
using Rubberduck.VBEditor.SafeComWrappers;
12+
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
13+
using RubberduckTests.Mocks;
14+
15+
namespace RubberduckTests.UnitTesting
16+
{
17+
internal class MockedTestEngine : IDisposable
18+
{
19+
public delegate void RunTestMethodCallback(ITypeLibWrapper wrapper, TestMethod method, EventHandler<AssertCompletedEventArgs> assertListener, out long duration);
20+
public delegate (long Duration, TestResult result) ReturnTestResult(ITypeLibWrapper wrapper, TestMethod method, EventHandler<AssertCompletedEventArgs> assertListener, out long duration);
21+
22+
private const string TestMethodTemplate =
23+
@"'@TestMethod
24+
Public Sub TestMethod{0}()
25+
End Sub";
26+
27+
private const string IgnoredTestTemplate =
28+
@"'@TestMethod
29+
'@IgnoreTest
30+
Public Sub TestMethod{0}()
31+
End Sub";
32+
33+
private const string TestMethodCategoryTemplate =
34+
@"'@TestMethod(""{1}"")
35+
Public Sub TestMethod{0}()
36+
End Sub";
37+
38+
private const string IgnoredTestCategoryTemplate =
39+
@"'@TestMethod(""{1}"")
40+
'@IgnoreTest
41+
Public Sub TestMethod{0}()
42+
End Sub";
43+
44+
private readonly Mock<IFakesFactory> _fakesFactory = new Mock<IFakesFactory>();
45+
private readonly Mock<IFakes> _createdFakes = new Mock<IFakes>();
46+
private long _durationStub;
47+
48+
private MockedTestEngine()
49+
{
50+
Dispatcher.Setup(d => d.InvokeAsync(It.IsAny<Action>())).Callback((Action action) => action.Invoke()).Verifiable();
51+
52+
TypeLib.Setup(tlm => tlm.Dispose()).Verifiable();
53+
WrapperProvider.Setup(p => p.TypeLibWrapperFromProject(It.IsAny<string>())).Returns(TypeLib.Object).Verifiable();
54+
55+
_fakesFactory.Setup(factory => factory.Create()).Returns(_createdFakes.Object);
56+
}
57+
58+
public MockedTestEngine(string testModuleCode) : this()
59+
{
60+
var builder = new MockVbeBuilder()
61+
.ProjectBuilder("TestProject1", ProjectProtection.Unprotected)
62+
.AddComponent("TestModule1", ComponentType.StandardModule, TestModuleHeader + testModuleCode)
63+
.AddProjectToVbeBuilder();
64+
65+
Vbe = builder.Build();
66+
ParserState = MockParser.Create(Vbe.Object).State;
67+
TestEngine = new TestEngine(ParserState, _fakesFactory.Object, VbeInteraction.Object, WrapperProvider.Object, Dispatcher.Object, Vbe.Object);
68+
}
69+
70+
public MockedTestEngine(IReadOnlyList<string> moduleNames, IReadOnlyList<int> methodCounts) : this()
71+
{
72+
if (moduleNames.Count != methodCounts.Count)
73+
{
74+
Assert.Inconclusive("Test setup error.");
75+
}
76+
77+
var builder = new MockVbeBuilder();
78+
var project = builder.ProjectBuilder("TestProject1", ProjectProtection.Unprotected);
79+
80+
for (var index = 0; index < moduleNames.Count; index++)
81+
{
82+
var testModuleCode = string.Join(Environment.NewLine, Enumerable.Range(1, methodCounts[index]).Select(num => GetTestMethod(num)));
83+
84+
project.AddComponent(moduleNames[index], ComponentType.StandardModule, TestModuleHeader + testModuleCode);
85+
}
86+
87+
project.AddProjectToVbeBuilder();
88+
Vbe = builder.Build();
89+
ParserState = MockParser.Create(Vbe.Object).State;
90+
TestEngine = new TestEngine(ParserState, _fakesFactory.Object, VbeInteraction.Object, WrapperProvider.Object, Dispatcher.Object, Vbe.Object);
91+
}
92+
93+
public MockedTestEngine(int testMethodCount)
94+
: this(string.Join(Environment.NewLine, Enumerable.Range(1, testMethodCount).Select(num => GetTestMethod(num))))
95+
{ }
96+
97+
public MockedTestEngine(List<(TestOutcome Outcome, string Output, long duration)> results)
98+
: this(string.Join(Environment.NewLine, Enumerable.Range(1, results.Count).Select(num => GetTestMethod(num, results[num - 1].Outcome == TestOutcome.Ignored))))
99+
{
100+
ParserState.OnParseRequested(this);
101+
var testMethodCount = results.Count;
102+
var testMethods = TestEngine.Tests.ToList();
103+
104+
if (testMethods.Count != testMethodCount)
105+
{
106+
Assert.Inconclusive("Test setup failure.");
107+
}
108+
109+
for (var test = 0; test < results.Count; test++)
110+
{
111+
var (outcome, output, duration) = results[test];
112+
SetupAssertCompleted(testMethods[test], new TestResult(outcome, output, duration));
113+
}
114+
}
115+
116+
public RubberduckParserState ParserState { get; set; }
117+
118+
public Mock<IVBE> Vbe { get; set; }
119+
120+
public ITestEngine TestEngine { get; set; }
121+
122+
public Mock<IVBEInteraction> VbeInteraction { get; } = new Mock<IVBEInteraction>();
123+
124+
public Mock<ITypeLibWrapper> TypeLib { get; } = new Mock<ITypeLibWrapper>();
125+
126+
public Mock<IUiDispatcher> Dispatcher { get; } = new Mock<IUiDispatcher>();
127+
128+
public Mock<ITypeLibWrapperProvider> WrapperProvider { get; } = new Mock<ITypeLibWrapperProvider>();
129+
130+
public static string GetTestMethod(int number, bool ignored = false, string category = null) =>
131+
category is null
132+
? string.Format(ignored ? IgnoredTestTemplate : TestMethodTemplate, number)
133+
: string.Format(ignored ? IgnoredTestCategoryTemplate : TestMethodCategoryTemplate, number, category);
134+
135+
public void SetupAssertCompleted(Action action)
136+
{
137+
if (action is null)
138+
{
139+
VbeInteraction.Setup(ia => ia.RunTestMethod(TypeLib.Object, It.IsAny<TestMethod>(), It.IsAny<EventHandler<AssertCompletedEventArgs>>(), out _durationStub))
140+
.Verifiable();
141+
return;
142+
}
143+
144+
var callback = GetAssertCompletedCallback(action);
145+
146+
VbeInteraction
147+
.Setup(ia => ia.RunTestMethod(TypeLib.Object, It.IsAny<TestMethod>(), It.IsAny<EventHandler<AssertCompletedEventArgs>>(), out _durationStub))
148+
.Callback(callback)
149+
.Verifiable();
150+
}
151+
152+
[SuppressMessage("ReSharper", "ImplicitlyCapturedClosure")] //This is fine - the closures' lifespan is limited to the test.
153+
public void SetupAssertCompleted(TestMethod testMethod, TestResult result)
154+
{
155+
Action action;
156+
switch (result.Outcome)
157+
{
158+
// Tests involving this case have concurrency issues when the UIDispatcher is mocked. The callbacks in production
159+
// are moderated by flushing the message queue. There is no way to mock this behavior because in the testing environment
160+
// there is no STA boundary being crossed, so the internal implementation of the AssertHandler is not constrained by
161+
// the rental context that would normally be managed by Interop. These tests *do* pass as of the commit that adds them,
162+
// but only if the context is force by running them through the debugger. Leaving these in as commented code mainly for
163+
// the purpose of documenting this. Single asserts are fine.
164+
165+
//case TestOutcome.SpectacularFail:
166+
// action = () =>
167+
// {
168+
// for (var failure = 0; failure < 10; failure++)
169+
// {
170+
// AssertHandler.OnAssertFailed(result.Output, testMethod.Declaration.IdentifierName);
171+
// }
172+
// };
173+
// break;
174+
case TestOutcome.Failed:
175+
action = () => AssertHandler.OnAssertFailed(result.Output, testMethod.Declaration.IdentifierName);
176+
break;
177+
case TestOutcome.Inconclusive:
178+
action = () => AssertHandler.OnAssertInconclusive(result.Output);
179+
break;
180+
case TestOutcome.Succeeded:
181+
action = AssertHandler.OnAssertSucceeded;
182+
break;
183+
default:
184+
action = () => { };
185+
break;
186+
}
187+
188+
var callback = new RunTestMethodCallback((ITypeLibWrapper _, TestMethod method, EventHandler<AssertCompletedEventArgs> assertHandler, out long duration) =>
189+
{
190+
duration = 0;
191+
AssertHandler.OnAssertCompleted += assertHandler;
192+
action.Invoke();
193+
AssertHandler.OnAssertCompleted -= assertHandler;
194+
});
195+
196+
VbeInteraction
197+
.Setup(ia => ia.RunTestMethod(TypeLib.Object, It.Is<TestMethod>(test => testMethod.Equals(test)), It.IsAny<EventHandler<AssertCompletedEventArgs>>(), out _durationStub))
198+
.Callback(callback)
199+
.Verifiable();
200+
}
201+
202+
public RunTestMethodCallback GetAssertCompletedCallback(Action action)
203+
{
204+
return (ITypeLibWrapper _, TestMethod method, EventHandler<AssertCompletedEventArgs> assertHandler, out long duration) =>
205+
{
206+
duration = 0;
207+
AssertHandler.OnAssertCompleted += assertHandler;
208+
action.Invoke();
209+
AssertHandler.OnAssertCompleted -= assertHandler;
210+
};
211+
}
212+
213+
public void Dispose()
214+
{
215+
ParserState?.Dispose();
216+
}
217+
218+
public const string TestModuleHeader = @"Option Explicit
219+
Option Private Module
220+
221+
'@TestModule
222+
223+
Private Assert As Object
224+
225+
'@ModuleInitialize
226+
Public Sub ModuleInitialize()
227+
'this method runs once per module.
228+
Assert = CreateObject(""Rubberduck.AssertClass"")
229+
End Sub
230+
231+
'@ModuleCleanup
232+
Public Sub ModuleCleanup()
233+
'this method runs once per module.
234+
End Sub
235+
236+
'@TestInitialize
237+
Public Sub TestInitialize()
238+
'this method runs before every test in the module.
239+
End Sub
240+
241+
'@TestCleanup
242+
Public Sub TestCleanup()
243+
'this method runs after every test in the module.
244+
End Sub
245+
";
246+
}
247+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System;
2+
using Moq;
3+
using Rubberduck.Common;
4+
using Rubberduck.Parsing.VBA;
5+
using Rubberduck.UI.UnitTesting;
6+
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
7+
8+
namespace RubberduckTests.UnitTesting
9+
{
10+
internal class MockedTestExplorer : IDisposable
11+
{
12+
public MockedTestExplorer(MockedTestExplorerModel model)
13+
{
14+
Vbe = model.Engine.Vbe.Object;
15+
State = model.Engine.ParserState;
16+
Model = model.Model;
17+
ViewModel = new TestExplorerViewModel(State.ProjectsProvider, Model, ClipboardWriter.Object, null, null);
18+
}
19+
20+
public RubberduckParserState State { get; set; }
21+
22+
public IVBE Vbe { get; }
23+
24+
public TestExplorerViewModel ViewModel { get; set; }
25+
26+
public TestExplorerModel Model { get; set; }
27+
28+
public Mock<IClipboardWriter> ClipboardWriter { get; } = new Mock<IClipboardWriter>();
29+
30+
public void Dispose()
31+
{
32+
Model?.Dispose();
33+
ViewModel?.Dispose();
34+
}
35+
}
36+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using NUnit.Framework;
5+
using Rubberduck.UI.UnitTesting;
6+
using Rubberduck.UnitTesting;
7+
8+
namespace RubberduckTests.UnitTesting
9+
{
10+
internal class MockedTestExplorerModel : IDisposable
11+
{
12+
public MockedTestExplorerModel(MockedTestEngine engine)
13+
{
14+
Engine = engine;
15+
Model = new TestExplorerModel(Engine.TestEngine);
16+
}
17+
18+
public MockedTestExplorerModel(List<(TestOutcome Outcome, string Output, long duration)> results)
19+
{
20+
var code = string.Join(Environment.NewLine,
21+
Enumerable.Range(1, results.Count).Select(num =>
22+
MockedTestEngine.GetTestMethod(num, results[num - 1].Outcome == TestOutcome.Ignored)));
23+
24+
Engine = new MockedTestEngine(code);
25+
Model = new TestExplorerModel(Engine.TestEngine);
26+
Engine.ParserState.OnParseRequested(this);
27+
28+
var testMethodCount = results.Count;
29+
var testMethods = Engine.TestEngine.Tests.ToList();
30+
31+
if (testMethods.Count != testMethodCount)
32+
{
33+
Assert.Inconclusive("Test setup failure.");
34+
}
35+
36+
for (var test = 0; test < results.Count; test++)
37+
{
38+
var (outcome, output, duration) = results[test];
39+
Engine.SetupAssertCompleted(testMethods[test], new TestResult(outcome, output, duration));
40+
}
41+
}
42+
43+
public TestExplorerModel Model { get; set; }
44+
45+
public MockedTestEngine Engine { get; set; }
46+
47+
public void Dispose()
48+
{
49+
Engine?.Dispose();
50+
Model?.Dispose();
51+
}
52+
}
53+
}

0 commit comments

Comments
 (0)