diff --git a/UnitTestProject1/UnitTestProject1.csproj b/NSpec.Runner.Tests/NSpec.Runner.Tests.csproj
similarity index 95%
rename from UnitTestProject1/UnitTestProject1.csproj
rename to NSpec.Runner.Tests/NSpec.Runner.Tests.csproj
index 16fd2fc..11644dd 100644
--- a/UnitTestProject1/UnitTestProject1.csproj
+++ b/NSpec.Runner.Tests/NSpec.Runner.Tests.csproj
@@ -6,8 +6,8 @@
{69395747-4A18-4A09-92F9-EED4032F7677}
Library
Properties
- UnitTestProject1
- UnitTestProject1
+ NSpec.Runner.Tests
+ NSpec.Runner.Tests
v4.5
512
{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
@@ -59,8 +59,8 @@
-
-
+
+
@@ -70,7 +70,9 @@
-
+
+ Designer
+
diff --git a/NSpec.Runner.Tests/NSpecRunnerTest.cs b/NSpec.Runner.Tests/NSpecRunnerTest.cs
new file mode 100644
index 0000000..dd4480b
--- /dev/null
+++ b/NSpec.Runner.Tests/NSpecRunnerTest.cs
@@ -0,0 +1,87 @@
+using System;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using NSpec;
+using NSpec.Runner;
+
+namespace NSpec.Runner.Tests
+{
+ [TestClass]
+ public class NSpecRunnerTest : nspec
+ {
+
+ [TestMethod, TestSpecification]
+ public void When_Writing_A_Test_Spec()
+ {
+ bool theTruth = false;
+
+ context["and when dealing with other people,"] = () =>
+ {
+ before = () => theTruth = true;
+ it["it will be honest."] = () => theTruth.should_be(true);
+ it["it will open doors for old ladies."] = () => theTruth.should_be(true);
+
+ context["and when dealing with old dudes that are grumpy,"] = () =>
+ {
+ before = () => theTruth = false;
+
+ it["it will tell a knock knock joke."] = () => theTruth.should_be(false);
+ };
+
+ context["and when dealing with old dudes that are not grumpy,"] = () =>
+ {
+ it["it will give them a high five."] = () => theTruth.should_be(true);
+ };
+ };
+ }
+
+ [TestMethod, TestSpecification]
+ public void When_Writing_A_Test_Spec_With_Failues()
+ {
+ bool theTruth = false;
+
+ context["and when dealing with other people,"] = () =>
+ {
+ before = () => theTruth = false;
+ it["it will be honest."] = () => theTruth.should_be(true);
+ it["it will open doors for old ladies."] = () => theTruth.should_be(true);
+
+ context["and when dealing with old dudes that are grumpy,"] = () =>
+ {
+ before = () => theTruth = true;
+
+ it["it will tell a knock knock joke."] = () => theTruth.should_be(false);
+ };
+
+ context["and when dealing with old dudes that are not grumpy,"] = () =>
+ {
+ it["it will give them a high five."] = () => theTruth.should_be(true);
+ };
+ };
+ }
+
+ [TestMethod, TestSpecification]
+ public void When_Writing_A_Test_Spec_With_Pendings()
+ {
+ context["and you haven't finished the specs"] = () =>
+ {
+ it["you should declare them as TODO"] = todo;
+ it["and the test result should be inconclusiv"] = () => { };
+ it["and the output should contain Pending = 1"] = () => { };
+ };
+ }
+
+ [TestMethod, TestSpecification, Ignore]
+ public void When_Writing_A_Test_Spec_With_Ingnore_Attribute()
+ {
+ it["it should be ignored"] = () => { Assert.Fail("it should be ignored"); };
+ }
+
+ [TestMethod, TestSpecification(FailFast=true)]
+ public void When_Writing_A_Test_Spec_With_FailFast()
+ {
+ it["it should fail on first assertation"] = () => { 1.is_greater_than(2); };
+ it["and not here"] = () => { 2.is_less_than(1); Assert.Fail("this should not be called"); };
+ }
+
+ }
+}
diff --git a/UnitTestProject1/Properties/AssemblyInfo.cs b/NSpec.Runner.Tests/Properties/AssemblyInfo.cs
similarity index 93%
rename from UnitTestProject1/Properties/AssemblyInfo.cs
rename to NSpec.Runner.Tests/Properties/AssemblyInfo.cs
index 7de63fb..51488d6 100644
--- a/UnitTestProject1/Properties/AssemblyInfo.cs
+++ b/NSpec.Runner.Tests/Properties/AssemblyInfo.cs
@@ -5,11 +5,11 @@
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
-[assembly: AssemblyTitle("UnitTestProject1")]
+[assembly: AssemblyTitle("NSpec.Runner.Tests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("UnitTestProject1")]
+[assembly: AssemblyProduct("NSpec.Runner.Tests")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
diff --git a/NSpec.Runner.Tests/TestSpecification.cs b/NSpec.Runner.Tests/TestSpecification.cs
new file mode 100644
index 0000000..5760bf9
--- /dev/null
+++ b/NSpec.Runner.Tests/TestSpecification.cs
@@ -0,0 +1,49 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Reflection;
+
+namespace NSpec.Runner.Tests
+{
+ [Serializable, TestClass]
+ public class TestSpecification : global::NSpec.Runner.TestSpecification
+ {
+ public static TestContext TestContext { get; protected set; }
+
+ [AssemblyInitialize]
+ public static void AssemblyInit(TestContext context)
+ {
+ // by marking this class as TestClass and adding an AssemblyInitialize attribut
+ // we can reliably determine if run via Test Explorer (or hopefully any other MsTest compatible runner)
+ //
+ // if TestContext != null Enabled will be false
+ // and test will be processed by console runner
+ TestContext = context;
+ }
+
+ public override void ProcessResults(NSpecResults results)
+ {
+ if (results.HasFailures)
+ Assert.Fail(results.Message);
+ else if (results.HasPendings)
+ Assert.Inconclusive(results.Message);
+ }
+
+ public override bool ShouldIgnore(MethodBase method)
+ {
+ return method.GetCustomAttribute() != null;
+ }
+
+ public override bool Enabled
+ {
+ get
+ {
+ return TestContext != null;
+ }
+ }
+
+ }
+}
diff --git a/UnitTestProject1/packages.config b/NSpec.Runner.Tests/packages.config
similarity index 100%
rename from UnitTestProject1/packages.config
rename to NSpec.Runner.Tests/packages.config
diff --git a/NSpec.Runner.sln b/NSpec.Runner.sln
index a589fc7..2a4822d 100644
--- a/NSpec.Runner.sln
+++ b/NSpec.Runner.sln
@@ -1,11 +1,11 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 2013
-VisualStudioVersion = 12.0.30723.0
+# Visual Studio 14
+VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NSpec.Runner", "NSpec.Runner\NSpec.Runner.csproj", "{4FD94255-9A5E-4B63-8F82-337F4FF0FC24}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTestProject1", "UnitTestProject1\UnitTestProject1.csproj", "{69395747-4A18-4A09-92F9-EED4032F7677}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NSpec.Runner.Tests", "NSpec.Runner.Tests\NSpec.Runner.Tests.csproj", "{69395747-4A18-4A09-92F9-EED4032F7677}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -25,4 +25,7 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(CodealikeProperties) = postSolution
+ SolutionGuid = 651d86ea-12f9-4d34-920b-903e59654049
+ EndGlobalSection
EndGlobal
diff --git a/NSpec.Runner/NSpec.Runner.csproj b/NSpec.Runner/NSpec.Runner.csproj
index 935d5af..4ba0053 100644
--- a/NSpec.Runner/NSpec.Runner.csproj
+++ b/NSpec.Runner/NSpec.Runner.csproj
@@ -51,6 +51,7 @@
+
@@ -60,6 +61,11 @@
+
+
+ nunit.framework.dll
+
+
diff --git a/NSpec.Runner/NSpecResult.cs b/NSpec.Runner/NSpecResult.cs
new file mode 100644
index 0000000..f98d168
--- /dev/null
+++ b/NSpec.Runner/NSpecResult.cs
@@ -0,0 +1,37 @@
+using NSpec.Domain;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NSpec.Runner
+{
+ [Serializable]
+ public class NSpecResults
+ {
+ private readonly string message;
+ private readonly ContextCollection results;
+
+ internal NSpecResults(ContextCollection results)
+ {
+ this.results = results;
+ this.message = String.Format("{0} Examples, {1} Failed, {2} Pending",
+ results.Examples().Count(),
+ results.Failures().Count(),
+ results.Pendings().Count()
+ );
+ }
+
+
+ public bool HasFailures { get { return results.Failures().Any(); } }
+
+ public bool HasPendings { get { return results.Pendings().Any(); } }
+
+ public bool HasExamples { get { return results.Examples().Any(); } }
+
+ public string Message { get { return message; } }
+
+ }
+
+}
diff --git a/NSpec.Runner/TestFailedException.cs b/NSpec.Runner/TestFailedException.cs
index 975b206..884d6d7 100644
--- a/NSpec.Runner/TestFailedException.cs
+++ b/NSpec.Runner/TestFailedException.cs
@@ -4,5 +4,7 @@ namespace NSpec.Runner
{
public class TestFailedException : Exception
{
+ public TestFailedException(string message) : base(message) { }
+ public TestFailedException(string message, Exception innerException) : base(message, innerException) { }
}
}
diff --git a/NSpec.Runner/TestSpecification.cs b/NSpec.Runner/TestSpecification.cs
index 87846fe..45d98b4 100644
--- a/NSpec.Runner/TestSpecification.cs
+++ b/NSpec.Runner/TestSpecification.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Xml.Serialization;
@@ -19,11 +20,38 @@ public class TestSpecification : MethodInterceptionAspect
[XmlArrayItem(Type = typeof(MethodBase))]
[XmlArray]
private static List methodsThatHaveBeenPrepared = new List();
+
+ static TestSpecification()
+ {
+ // NSpec.AssertionExtensions methods are wrappers for nunit assertations but NSpec has no direct dependency on nunit.framework itself.
+ // That's why we include nunit.framework.dll from packages\nspec.1.0.5\tools as an embedded resource
+ // and use AssemblyResolve event to dynamically load it at runtime
+ // otherwise specs would wile with a FileNotFoundException
+ AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
+ }
+
+ private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
+ {
+ // https://blogs.msdn.microsoft.com/microsoft_press/2010/02/03/jeffrey-richter-excerpt-2-from-clr-via-c-third-edition/
+ var assemblyName = new AssemblyName(args.Name);
+ String resourceName = "NSpec.Runner." + assemblyName.Name + ".dll";
+ using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
+ {
+ if (stream == null) return null;
+ Byte[] assemblyData = new Byte[stream.Length];
+ stream.Read(assemblyData, 0, assemblyData.Length);
+ return Assembly.Load(assemblyData);
+ }
+ }
public override void OnInvoke(MethodInterceptionArgs args)
{
+
+ // skip ignored specs
+ if (ShouldIgnore(args.Method)) return;
+
// if the method has been prepared, we need to actually execute the function
- if (methodsThatHaveBeenPrepared.Contains(args.Method))
+ if (methodsThatHaveBeenPrepared.Contains(args.Method) || !Enabled)
{
base.OnInvoke(args);
return;
@@ -35,7 +63,8 @@ public override void OnInvoke(MethodInterceptionArgs args)
// prepare the npsec content of the function
var finder = new SingleClassGetter(args.Instance.GetType());
var builder = new MethodContextBuilder(finder, new DefaultConventions());
- var runner = new MethodContextRunner(builder, new ConsoleFormatter(), false);
+ var formatter = new ConsoleFormatter(); // { WriteLineDelegate = value => Console.WriteLine(Regex.Replace(value, @"\r(?!\n)", Environment.NewLine)) }; // custom formatter to replace \r with newline
+ var runner = new MethodContextRunner(builder, formatter, this.FailFast);
// set up the contexts for the method
var methodInfo = args.Method as MethodInfo;
@@ -45,13 +74,50 @@ public override void OnInvoke(MethodInterceptionArgs args)
var builtContexts = contexts.Build();
var results = runner.Run(builtContexts);
+ // wrap result and handle
+ var nspecResults = new NSpecResults(results);
+ ProcessResults(nspecResults);
+
+ }
+
+ ///
+ /// Override to add Test Framework specific result handling.
+ ///
+ ///
+ public virtual void ProcessResults(NSpecResults results)
+ {
// if there were any failures, raise the exception so that the test framework has an error
- if (results.Failures().Any())
- {
- throw new TestFailedException();
- }
+ if (results.HasFailures)
+ throw new TestFailedException(results.Message);
// tests all passed
}
+
+ ///
+ /// Determine if a certain test should be ignored
+ ///
+ ///
+ ///
+ public virtual bool ShouldIgnore(MethodBase method)
+ {
+ return false;
+ }
+
+ ///
+ /// If false specs are not intercepted. Defaults to true, unless running from NSpecRunner.exe
+ ///
+ public virtual bool Enabled
+ {
+ get
+ {
+ return !Process.GetCurrentProcess().ProcessName.Equals("NSpecRunner", StringComparison.OrdinalIgnoreCase);
+ }
+ }
+
+ ///
+ /// Configue if test should fail fast or not. Only applies if run if Enabled
+ ///
+ public virtual bool FailFast { get; set; }
+
}
}
diff --git a/UnitTestProject1/UnitTest1.cs b/UnitTestProject1/UnitTest1.cs
deleted file mode 100644
index c5348ea..0000000
--- a/UnitTestProject1/UnitTest1.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using NSpec;
-using NSpec.Runner;
-
-namespace UnitTestProject1
-{
- [TestClass]
- public class MSTest1 : nspec
- {
- private string something = String.Empty;
-
- [TestMethod, TestSpecification]
- public void When_Writing_A_Test_Spec()
- {
- string theTruth = String.Empty;
-
- context["and when dealing with other people,"] = () =>
- {
- before = () => theTruth = "true";
- it["it will be honest."] = () => Assert.AreEqual(theTruth, "true");
- it["it will open doors for old ladies."] = () => Assert.AreEqual(theTruth, "true");
-
- context["and when dealing with old dudes that are grumpy,"] = () =>
- {
- before = () => theTruth = "false";
-
- it["it will tell a knock knock joke."] = () => Assert.AreEqual(theTruth, "false");
- };
-
- context["and when dealing with old dudes that are not grumpy,"] = () =>
- {
- it["it will give them a high five."] = () => Assert.AreEqual(theTruth, "true");
- };
- };
- }
- }
-}