From fd78e4fa1e20458dfc6912611d423220c364424e Mon Sep 17 00:00:00 2001 From: Bogdan Szabo Date: Fri, 22 Jan 2021 12:22:50 +0100 Subject: [PATCH] add instance of comparison --- source/fluentasserts/core/base.d | 20 ++-- source/fluentasserts/core/callable.d | 1 - source/fluentasserts/core/evaluation.d | 74 +++++++++++++- source/fluentasserts/core/expect.d | 16 ++- source/fluentasserts/core/lifecycle.d | 10 +- source/fluentasserts/core/objects.d | 98 ++++--------------- .../core/operations/approximately.d | 2 +- source/fluentasserts/core/operations/beNull.d | 8 +- .../core/operations/instanceOf.d | 51 ++++++++++ .../fluentasserts/core/operations/registry.d | 18 +++- source/fluentasserts/core/serializers.d | 33 ++++++- test/operations/approximately.d | 8 +- test/operations/beNull.d | 1 - test/operations/instanceOf.d | 65 ++++++++++++ 14 files changed, 297 insertions(+), 108 deletions(-) create mode 100644 source/fluentasserts/core/operations/instanceOf.d create mode 100644 test/operations/instanceOf.d diff --git a/source/fluentasserts/core/base.d b/source/fluentasserts/core/base.d index 4febe8b..a27bac9 100644 --- a/source/fluentasserts/core/base.d +++ b/source/fluentasserts/core/base.d @@ -501,7 +501,7 @@ auto should(T)(lazy T testData, const string file = __FILE__, const size_t line } else static if(!returned) { static if(is(T == class) || is(T == interface)) { - return ShouldObject!T(testData.evaluate); + return expect(testData, file, line); } else { return expect(testData, file, line); } @@ -520,7 +520,7 @@ unittest { struct Assert { static void opDispatch(string s, T, U)(T actual, U expected, string reason = "", const string file = __FILE__, const size_t line = __LINE__) { - auto sh = actual.should; + auto sh = expect(actual); static if(s[0..3] == "not") { sh.not; @@ -548,7 +548,7 @@ struct Assert { static void between(T, U)(T actual, U begin, U end, string reason = "", const string file = __FILE__, const size_t line = __LINE__) { - auto s = actual.should.be.between(begin, end); + auto s = expect(actual, file, line).to.be.between(begin, end); if(reason != "") { s.because(reason); @@ -557,7 +557,7 @@ struct Assert { static void notBetween(T, U)(T actual, U begin, U end, string reason = "", const string file = __FILE__, const size_t line = __LINE__) { - auto s = actual.should.not.be.between(begin, end); + auto s = expect(actual, file, line).not.to.be.between(begin, end); if(reason != "") { s.because(reason); @@ -566,7 +566,7 @@ struct Assert { static void within(T, U)(T actual, U begin, U end, string reason = "", const string file = __FILE__, const size_t line = __LINE__) { - auto s = actual.should.be.within(begin, end); + auto s = expect(actual, file, line).to.be.between(begin, end); if(reason != "") { s.because(reason); @@ -575,7 +575,7 @@ struct Assert { static void notWithin(T, U)(T actual, U begin, U end, string reason = "", const string file = __FILE__, const size_t line = __LINE__) { - auto s = actual.should.not.be.within(begin, end); + auto s = expect(actual, file, line).not.to.be.between(begin, end); if(reason != "") { s.because(reason); @@ -584,7 +584,7 @@ struct Assert { static void approximately(T, U, V)(T actual, U expected, V delta, string reason = "", const string file = __FILE__, const size_t line = __LINE__) { - auto s = actual.should.be.approximately(expected, delta); + auto s = expect(actual, file, line).to.be.approximately(expected, delta); if(reason != "") { s.because(reason); @@ -593,7 +593,7 @@ struct Assert { static void notApproximately(T, U, V)(T actual, U expected, V delta, string reason = "", const string file = __FILE__, const size_t line = __LINE__) { - auto s = actual.should.not.be.approximately(expected, delta); + auto s = expect(actual, file, line).not.to.be.approximately(expected, delta); if(reason != "") { s.because(reason); @@ -602,7 +602,7 @@ struct Assert { static void beNull(T)(T actual, string reason = "", const string file = __FILE__, const size_t line = __LINE__) { - auto s = actual.should.beNull; + auto s = expect(actual, file, line).to.beNull; if(reason != "") { s.because(reason); @@ -611,7 +611,7 @@ struct Assert { static void notNull(T)(T actual, string reason = "", const string file = __FILE__, const size_t line = __LINE__) { - auto s = actual.should.not.beNull(file, line); + auto s = expect(actual, file, line).not.to.beNull; if(reason != "") { s.because(reason); diff --git a/source/fluentasserts/core/callable.d b/source/fluentasserts/core/callable.d index c66b248..7fb74ea 100644 --- a/source/fluentasserts/core/callable.d +++ b/source/fluentasserts/core/callable.d @@ -124,7 +124,6 @@ unittest { }).should.throwException!CustomException.thrown; thrown.should.not.beNull; - thrown.should.be.instanceOf!CustomException; thrown.msg.should.equal("test"); (cast(CustomException) thrown).data.should.equal(2); } diff --git a/source/fluentasserts/core/evaluation.d b/source/fluentasserts/core/evaluation.d index 87e7178..ffbd6c1 100644 --- a/source/fluentasserts/core/evaluation.d +++ b/source/fluentasserts/core/evaluation.d @@ -6,6 +6,7 @@ import std.traits; import std.conv; import std.range; import std.array; +import std.algorithm : map; import fluentasserts.core.serializers; import fluentasserts.core.results; @@ -26,10 +27,18 @@ struct ValueEvaluation { string niceValue; /// The name of the type before it was converted to string - string typeName; + string[] typeNames; /// Other info about the value string[string] meta; + + string typeName() @safe nothrow { + if(typeNames.length == 0) { + return "unknown"; + } + + return typeNames[0]; + } } /// @@ -89,7 +98,7 @@ auto evaluate(T)(lazy T testData) @trusted if(!isInputRange!T || isArray!T || is auto duration = Clock.currTime - begin; auto serializedValue = SerializerRegistry.instance.serialize(value); auto niceValue = SerializerRegistry.instance.niceValue(value); - return Result(value, ValueEvaluation(null, duration, serializedValue, niceValue, unqualString!TT)); + return Result(value, ValueEvaluation(null, duration, serializedValue, niceValue, extractTypes!TT )); } catch(Throwable t) { T result; @@ -97,7 +106,7 @@ auto evaluate(T)(lazy T testData) @trusted if(!isInputRange!T || isArray!T || is result = testData; } - return Result(result, ValueEvaluation(t, Clock.currTime - begin, result.to!string, result.to!string, unqualString!T)); + return Result(result, ValueEvaluation(t, Clock.currTime - begin, result.to!string, result.to!string, extractTypes!T )); } } @@ -124,3 +133,62 @@ unittest { assert(result.evaluation.throwable !is null); assert(result.evaluation.throwable.msg == "message"); } + +string[] extractTypes(T)() if((!isArray!T && !isAssociativeArray!T) || isSomeString!T) { + string[] types; + + types ~= unqualString!T; + + static if(is(T == class)) { + static foreach(Type; BaseClassesTuple!T) { + types ~= unqualString!Type; + } + } + + static if(is(T == interface) || is(T == class)) { + static foreach(Type; InterfacesTuple!T) { + types ~= unqualString!Type; + } + } + + return types; +} + +string[] extractTypes(T: U[], U)() if(isArray!T && !isSomeString!T) { + return extractTypes!(U).map!(a => a ~ "[]").array; +} + +string[] extractTypes(T: U[K], U, K)() { + string k = unqualString!(K); + return extractTypes!(U).map!(a => a ~ "[" ~ k ~ "]").array; +} + +/// It can get the type of a string +unittest { + auto result = extractTypes!string; + assert(result == ["string"]); +} + +/// It can get the type of a string list +unittest { + auto result = extractTypes!(string[]); + assert(result == ["string[]"]); +} + +/// It can get the type of a string assoc array +unittest { + auto result = extractTypes!(string[string]); + assert(result == ["string[string]"]); +} + +/// It can get all types of a class +unittest { + interface I {} + class T : I {} + + auto result = extractTypes!(T[]); + + assert(result[0] == "fluentasserts.core.evaluation.__unittest_L185_C1.T[]"); + assert(result[1] == "object.Object[]"); + assert(result[2] == "fluentasserts.core.evaluation.__unittest_L185_C1.I[]"); +} \ No newline at end of file diff --git a/source/fluentasserts/core/expect.d b/source/fluentasserts/core/expect.d index 396683b..41c300e 100644 --- a/source/fluentasserts/core/expect.d +++ b/source/fluentasserts/core/expect.d @@ -4,6 +4,8 @@ import fluentasserts.core.lifecycle; import fluentasserts.core.evaluation; import fluentasserts.core.results; +import fluentasserts.core.serializers; + import std.traits; import std.string; import std.uni; @@ -171,6 +173,14 @@ import std.conv; return opDispatch!"containOnly"(value); } + auto beNull() { + return opDispatch!"beNull"; + } + + auto instanceOf(Type)() { + return opDispatch!"instanceOf"(fullyQualifiedName!Type); + } + auto approximately(T, U)(T value, U range) { return opDispatch!"approximately"(value, range); } @@ -238,13 +248,13 @@ import std.conv; /// Expect expect(void delegate() callable, const string file = __FILE__, const size_t line = __LINE__, string prependText = null) @trusted { ValueEvaluation value; - value.typeName = "callable"; + value.typeNames = [ "callable" ]; try { if(callable !is null) { callable(); } else { - value.typeName = "null"; + value.typeNames = ["null"]; } } catch(Exception e) { value.throwable = e; @@ -294,4 +304,4 @@ unittest { expect("".toNiceOperation).to.equal(""); expect("a.b".toNiceOperation).to.equal("a b"); expect("aB".toNiceOperation).to.equal("a b"); -} \ No newline at end of file +} diff --git a/source/fluentasserts/core/lifecycle.d b/source/fluentasserts/core/lifecycle.d index d757bb5..425f1ef 100644 --- a/source/fluentasserts/core/lifecycle.d +++ b/source/fluentasserts/core/lifecycle.d @@ -4,21 +4,23 @@ import fluentasserts.core.base; import fluentasserts.core.evaluation; import fluentasserts.core.operations.approximately; import fluentasserts.core.operations.arrayEqual; +import fluentasserts.core.operations.beNull; import fluentasserts.core.operations.between; import fluentasserts.core.operations.contain; import fluentasserts.core.operations.endWith; import fluentasserts.core.operations.equal; import fluentasserts.core.operations.greaterThan; +import fluentasserts.core.operations.instanceOf; import fluentasserts.core.operations.lessThan; import fluentasserts.core.operations.registry; import fluentasserts.core.operations.startWith; import fluentasserts.core.operations.throwable; -import fluentasserts.core.operations.beNull; import fluentasserts.core.results; import fluentasserts.core.serializers; import std.meta; import std.conv; +import std.datetime; alias BasicNumericTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, float, double, real); alias NumericTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, float, double, real, ifloat, idouble, ireal, cfloat, cdouble, creal, char, wchar, dchar); @@ -32,8 +34,8 @@ static this() { Registry.instance = new Registry(); - Registry.instance.register("Duration", "Duration", "lessThan", &lessThanDuration); - Registry.instance.register("Duration", "Duration", "below", &lessThanDuration); + Registry.instance.register!(Duration, Duration)("lessThan", &lessThanDuration); + Registry.instance.register!(Duration, Duration)("below", &lessThanDuration); Registry.instance.register("string", "string", "equal", &equal); Registry.instance.register("bool", "bool", "equal", &equal); @@ -101,6 +103,8 @@ static this() { Registry.instance.register(Type.stringof, "char", "endWith", &endWith); } + Registry.instance.register("*", "*", "instanceOf", &instanceOf); + Registry.instance.register("callable", "", "throwAnyException", &throwAnyException); Registry.instance.register("callable", "", "throwException", &throwException); diff --git a/source/fluentasserts/core/objects.d b/source/fluentasserts/core/objects.d index 978d3f4..f1fdfc4 100644 --- a/source/fluentasserts/core/objects.d +++ b/source/fluentasserts/core/objects.d @@ -8,62 +8,6 @@ import std.stdio; import std.traits; import std.conv; -@safe: - -struct ShouldObject(T) { - private { - T testData; - } - - mixin ShouldCommons; - mixin ShouldThrowableCommons; - - this(U)(U value) { - this.valueEvaluation = value.evaluation; - this.testData = value.value; - } - - auto beNull(const string file = __FILE__, const size_t line = __LINE__) @trusted { - validateException; - - addMessage(" be "); - addValue("null"); - beginCheck; - - if(expectedValue) { - return result(testData is null, cast(IResult) new ExpectedActualResult("null", "a `" ~ T.stringof ~ "` instance"), file, line); - } else { - return result(testData is null, cast(IResult) new ExpectedActualResult("a `" ~ T.stringof ~ "` instance", "null"), file, line); - } - } - - auto instanceOf(U)(const string file = __FILE__, const size_t line = __LINE__) @trusted { - validateException; - - addValue(" instance of `" ~ U.stringof ~ "`"); - beginCheck; - - U castedObject = cast(U) testData; - - return result(castedObject !is null, - cast(IResult) new ExpectedActualResult(( expectedValue ? "" : "not " ) ~ "a `" ~ U.stringof ~ "` instance", - "a `" ~ T.stringof ~ "` instance"), - file, line); - } - - auto equal(U)(U instance, const string file = __FILE__, const size_t line = __LINE__) @trusted { - validateException; - - addMessage(" equal "); - addValue("`" ~ U.stringof ~ "`"); - beginCheck; - - return result(testData == instance, [] , - new ExpectedActualResult(( expectedValue ? "" : "not " ) ~ instance.to!string, testData.to!string), file, line); - } -} - - /// When there is a lazy object that throws an it should throw that exception unittest { Object someLazyObject() { @@ -97,8 +41,8 @@ unittest { }).should.throwException!TestException.msg; msg.split("\n")[0].should.equal("o should not be null."); - msg.split("\n")[2].strip.should.equal("Expected:a `Object` instance"); - msg.split("\n")[3].strip.should.equal("Actual:null"); + msg.split("\n")[2].strip.should.equal("Expected:not null"); + msg.split("\n")[3].strip.should.equal("Actual:object.Object"); msg = ({ (new Object).should.beNull; @@ -106,7 +50,7 @@ unittest { msg.split("\n")[0].should.equal("(new Object) should be null."); msg.split("\n")[2].strip.should.equal("Expected:null"); - msg.split("\n")[3].strip.strip.should.equal("Actual:a `Object` instance"); + msg.split("\n")[3].strip.strip.should.equal("Actual:object.Object"); } /// object instanceOf @@ -130,17 +74,17 @@ unittest { otherObject.should.be.instanceOf!SomeClass; }).should.throwException!TestException.msg; - msg.split("\n")[0].should.equal("otherObject should be instance of `SomeClass`."); - msg.split("\n")[2].strip.should.equal("Expected:a `SomeClass` instance"); - msg.split("\n")[3].strip.should.equal("Actual:a `OtherClass` instance"); + msg.split("\n")[0].should.startWith(`otherObject should be instance of "fluentasserts.core.objects.__unittest_L57_C1.SomeClass".`); + msg.split("\n")[2].strip.should.equal("Expected:typeof fluentasserts.core.objects.__unittest_L57_C1.SomeClass"); + msg.split("\n")[3].strip.should.equal("Actual:typeof fluentasserts.core.objects.__unittest_L57_C1.OtherClass"); msg = ({ otherObject.should.not.be.instanceOf!OtherClass; }).should.throwException!TestException.msg; - msg.split("\n")[0].should.equal("otherObject should not be instance of `OtherClass`."); - msg.split("\n")[2].strip.should.equal("Expected:not a `OtherClass` instance"); - msg.split("\n")[3].strip.should.equal("Actual:a `OtherClass` instance"); + msg.split("\n")[0].should.startWith(`otherObject should not be instance of "fluentasserts.core.objects.__unittest_L57_C1.OtherClass"`); + msg.split("\n")[2].strip.should.equal("Expected:not typeof fluentasserts.core.objects.__unittest_L57_C1.OtherClass"); + msg.split("\n")[3].strip.should.equal("Actual:typeof fluentasserts.core.objects.__unittest_L57_C1.OtherClass"); } /// object instanceOf interface @@ -154,7 +98,7 @@ unittest { auto otherObject = new OtherClass; someInterface.should.be.instanceOf!MyInterface; - someInterface.should.be.instanceOf!BaseClass; + someInterface.should.not.be.instanceOf!BaseClass; someObject.should.be.instanceOf!MyInterface; @@ -162,17 +106,17 @@ unittest { otherObject.should.be.instanceOf!MyInterface; }).should.throwException!TestException.msg; - msg.split("\n")[0].should.equal("otherObject should be instance of `MyInterface`."); - msg.split("\n")[2].strip.should.equal("Expected:a `MyInterface` instance"); - msg.split("\n")[3].strip.should.equal("Actual:a `OtherClass` instance"); + msg.split("\n")[0].should.startWith(`otherObject should be instance of "fluentasserts.core.objects.__unittest_L91_C1.MyInterface".`); + msg.split("\n")[2].strip.should.equal("Expected:typeof fluentasserts.core.objects.__unittest_L91_C1.MyInterface"); + msg.split("\n")[3].strip.should.equal("Actual:typeof fluentasserts.core.objects.__unittest_L91_C1.OtherClass"); msg = ({ someObject.should.not.be.instanceOf!MyInterface; }).should.throwException!TestException.msg; - msg.split("\n")[0].should.equal("someObject should not be instance of `MyInterface`."); - msg.split("\n")[2].strip.should.equal("Expected:not a `MyInterface` instance"); - msg.split("\n")[3].strip.should.equal("Actual:a `BaseClass` instance"); + msg.split("\n")[0].should.contain(`someObject should not be instance of "fluentasserts.core.objects.__unittest_L91_C1.MyInterface".`); + msg.split("\n")[2].strip.should.equal("Expected:not typeof fluentasserts.core.objects.__unittest_L91_C1.MyInterface"); + msg.split("\n")[3].strip.should.equal("Actual:typeof fluentasserts.core.objects.__unittest_L91_C1.BaseClass"); } /// should throw exceptions for delegates that return basic types @@ -192,7 +136,7 @@ unittest { try { noException.should.throwAnyException; } catch (TestException e) { - e.msg.should.startWith("noException should throw any exception. Nothing was thrown."); + e.msg.should.startWith("noException should throw any exception. No exception was thrown."); thrown = true; } @@ -218,13 +162,13 @@ unittest { instance.should.not.equal(instance); }).should.throwException!TestException.msg; - msg.should.startWith("instance should not equal `TestEqual`."); + msg.should.startWith("instance should not equal TestEqual"); msg = ({ instance.should.equal(new TestEqual(1)); }).should.throwException!TestException.msg; - msg.should.startWith("instance should equal `TestEqual`."); + msg.should.startWith("instance should equal TestEqual"); } /// null object comparison @@ -236,11 +180,11 @@ unittest nullObject.should.equal(new Object); }).should.throwException!TestException.msg; - msg.should.startWith("nullObject should equal `Object`."); + msg.should.startWith("nullObject should equal Object("); msg = ({ (new Object).should.equal(null); }).should.throwException!TestException.msg; - msg.should.startWith("(new Object) should equal `typeof(null)`."); + msg.should.startWith("(new Object) should equal null."); } diff --git a/source/fluentasserts/core/operations/approximately.d b/source/fluentasserts/core/operations/approximately.d index 140a49d..028d03d 100644 --- a/source/fluentasserts/core/operations/approximately.d +++ b/source/fluentasserts/core/operations/approximately.d @@ -27,7 +27,7 @@ IResult[] approximately(ref Evaluation evaluation) @trusted nothrow { real[] expectedPieces; bool usingArrays; - try usingArrays = evaluation.currentValue.typeName.canFind('['); + try usingArrays = !evaluation.currentValue.typeNames.filter!(a => a.canFind('[')).empty; catch(Exception) usingArrays = true; try { diff --git a/source/fluentasserts/core/operations/beNull.d b/source/fluentasserts/core/operations/beNull.d index c9386e1..27241e5 100644 --- a/source/fluentasserts/core/operations/beNull.d +++ b/source/fluentasserts/core/operations/beNull.d @@ -4,12 +4,13 @@ import fluentasserts.core.results; import fluentasserts.core.evaluation; import fluentasserts.core.lifecycle; +import std.algorithm; /// IResult[] beNull(ref Evaluation evaluation) @safe nothrow { evaluation.message.addText("."); - auto result = evaluation.currentValue.typeName == "null"; + auto result = evaluation.currentValue.typeNames.canFind("null") || evaluation.currentValue.strValue == "null"; if(evaluation.isNegated) { result = !result; @@ -21,7 +22,10 @@ IResult[] beNull(ref Evaluation evaluation) @safe nothrow { IResult[] results = []; - try results ~= new ExpectedActualResult((evaluation.isNegated ? "not null" : "null"), evaluation.currentValue.typeName); catch(Exception) {} + try results ~= new ExpectedActualResult( + evaluation.isNegated ? "not null" : "null", + evaluation.currentValue.typeNames.length ? evaluation.currentValue.typeNames[0] : "unknown"); + catch(Exception) {} return results; } diff --git a/source/fluentasserts/core/operations/instanceOf.d b/source/fluentasserts/core/operations/instanceOf.d new file mode 100644 index 0000000..6efcb08 --- /dev/null +++ b/source/fluentasserts/core/operations/instanceOf.d @@ -0,0 +1,51 @@ +module fluentasserts.core.operations.instanceOf; + +import fluentasserts.core.results; +import fluentasserts.core.evaluation; + +import fluentasserts.core.lifecycle; + +import std.conv; +import std.datetime; +import std.algorithm; + +version(unittest) { + import fluentasserts.core.expect; +} + +/// +IResult[] instanceOf(ref Evaluation evaluation) @safe nothrow { + string expectedType = evaluation.expectedValue.strValue[1 .. $-1]; + string currentType = evaluation.currentValue.typeNames[0]; + + evaluation.message.addText(". "); + + auto existingTypes = findAmong(evaluation.currentValue.typeNames, [expectedType]); + + import std.stdio; + + auto isExpected = existingTypes.length > 0; + + if(evaluation.isNegated) { + isExpected = !isExpected; + } + + IResult[] results = []; + + if(!isExpected) { + evaluation.message.addValue(evaluation.currentValue.strValue); + evaluation.message.addText(" is instance of "); + evaluation.message.addValue(currentType); + evaluation.message.addText("."); + } + + if(!isExpected && !evaluation.isNegated) { + try results ~= new ExpectedActualResult("typeof " ~ expectedType, "typeof " ~ currentType); catch(Exception) {} + } + + if(!isExpected && evaluation.isNegated) { + try results ~= new ExpectedActualResult("not typeof " ~ expectedType, "typeof " ~ currentType); catch(Exception) {} + } + + return results; +} \ No newline at end of file diff --git a/source/fluentasserts/core/operations/registry.d b/source/fluentasserts/core/operations/registry.d index d46aac0..f9236b9 100644 --- a/source/fluentasserts/core/operations/registry.d +++ b/source/fluentasserts/core/operations/registry.d @@ -19,6 +19,17 @@ class Registry { } /// Register a new assert operation + Registry register(T, U)(string name, Operation operation) { + foreach(valueType; extractTypes!T) { + foreach(expectedValueType; extractTypes!U) { + register(valueType, expectedValueType, name, operation); + } + } + + return this; + } + + /// ditto Registry register(string valueType, string expectedValueType, string name, Operation operation) { string key = valueType ~ "." ~ expectedValueType ~ "." ~ name; @@ -27,11 +38,16 @@ class Registry { return this; } - /// Register a new assert operation + /// ditto Registry register(string valueType, string expectedValueType, string name, IResult[] function(ref Evaluation) @safe nothrow operation) { return this.register(valueType, expectedValueType, name, operation.toDelegate); } + /// ditto + Registry register(T, U)(string name, IResult[] function(ref Evaluation) @safe nothrow operation) { + return this.register!(T, U)(name, operation.toDelegate); + } + /// Get an operation function Operation get(string valueType, string expectedValueType, string name) @safe nothrow { assert(valueType != "", "The value type is not set!"); diff --git a/source/fluentasserts/core/serializers.d b/source/fluentasserts/core/serializers.d index 7784acf..9882edc 100644 --- a/source/fluentasserts/core/serializers.d +++ b/source/fluentasserts/core/serializers.d @@ -169,7 +169,7 @@ unittest { } version(unittest) { struct TestStruct { int a; string b; }; } -/// It should serialzie a struct +/// It should serialize a struct unittest { TestStruct value = TestStruct(1, "2"); const TestStruct cvalue = TestStruct(1, "2"); @@ -189,7 +189,36 @@ string unqualString(T: V[K], V, K)() if(isAssociativeArray!T) { } string unqualString(T)() if(isSomeString!T || (!isArray!T && !isAssociativeArray!T)) { - return Unqual!T.stringof; + static if(is(T == class) || is(T == struct) || is(T == interface)) { + return fullyQualifiedName!(Unqual!(T)); + } else { + return Unqual!T.stringof; + } + +} + + +string joinClassTypes(T)() { + string result; + + static if(is(T == class)) { + static foreach(Type; BaseClassesTuple!T) { + result ~= Type.stringof; + } + } + + static if(is(T == interface) || is(T == class)) { + static foreach(Type; InterfacesTuple!T) { + if(result.length > 0) result ~= ":"; + result ~= Type.stringof; + } + } + + static if(!is(T == interface) && !is(T == class)) { + result = Unqual!T.stringof; + } + + return result; } /// diff --git a/test/operations/approximately.d b/test/operations/approximately.d index 0aa4b1e..472e192 100644 --- a/test/operations/approximately.d +++ b/test/operations/approximately.d @@ -38,7 +38,7 @@ alias s = Spec!({ expect(testValue).to.not.be.approximately(0.35, 0.001); }); - it("should show a detailed error message when two numbers should be approximatly equal but they are not", { + it("should show a detailed error message when two numbers should be approximately equal but they are not", { auto msg = ({ expect(testValue).to.be.approximately(0.35, 0.0001); }).should.throwException!TestException.msg; @@ -48,7 +48,7 @@ alias s = Spec!({ msg.should.not.contain("Missing:"); }); - it("should show a detailed error message when two numbers are approximatly equal but they should not", { + it("should show a detailed error message when two numbers are approximately equal but they should not", { auto msg = ({ expect(testValue).to.not.be.approximately(testValue, 0.0001); }).should.throwException!TestException.msg; @@ -80,7 +80,7 @@ alias s = Spec!({ expect(testValues).to.not.be.approximately([0.35, 0.50], 0.001); }); - it("should show a detailed error message when two lists should be approximatly equal but they are not", { + it("should show a detailed error message when two lists should be approximately equal but they are not", { auto msg = ({ expect(testValues).to.be.approximately([0.35, 0.50, 0.34], 0.0001); }).should.throwException!TestException.msg; @@ -89,7 +89,7 @@ alias s = Spec!({ msg.should.contain("Missing:[0.501±0.0001, 0.341±0.0001]"); }); - it("should show a detailed error message when two lists are approximatly equal but they should not", { + it("should show a detailed error message when two lists are approximately equal but they should not", { auto msg = ({ expect(testValues).to.not.be.approximately(testValues, 0.0001); }).should.throwException!TestException.msg; diff --git a/test/operations/beNull.d b/test/operations/beNull.d index 9b80db3..d493c31 100644 --- a/test/operations/beNull.d +++ b/test/operations/beNull.d @@ -12,7 +12,6 @@ import std.meta; alias s = Spec!({ describe("using delegates", { void delegate() value; - describe("when the delegate is set", { beforeEach({ void test() {} diff --git a/test/operations/instanceOf.d b/test/operations/instanceOf.d new file mode 100644 index 0000000..63772f0 --- /dev/null +++ b/test/operations/instanceOf.d @@ -0,0 +1,65 @@ +module test.operations.instanceOf; + +import fluentasserts.core.expect; +import fluent.asserts; + +import trial.discovery.spec; + +import std.string; +import std.conv; +import std.meta; + +alias s = Spec!({ + alias NumericTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, float, double, real); + + + it("should not throw when comparing an object", { + auto value = new Object(); + + expect(value).to.be.instanceOf!Object; + expect(value).to.not.be.instanceOf!string; + }); + + it("should not throw when comparing an Exception with an Object", { + auto value = new Exception("some test"); + + expect(value).to.be.instanceOf!Exception; + expect(value).to.be.instanceOf!Object; + expect(value).to.not.be.instanceOf!string; + }); + + static foreach(Type; NumericTypes) { + describe("using " ~ Type.stringof ~ " values", { + Type value; + + before({ + value = cast(Type) 40; + }); + + it("should be able to compare two types", { + expect(value).to.be.instanceOf!Type; + expect(value).to.not.be.instanceOf!string; + }); + + it("should throw a detailed error when the types do not match", { + auto msg = ({ + expect(value).to.be.instanceOf!string; + }).should.throwException!TestException.msg; + + msg.split("\n")[0].should.equal(value.to!string ~ ` should be instance of "string". ` ~ value.to!string ~ " is instance of " ~ Type.stringof ~ "."); + msg.split("\n")[2].strip.should.equal("Expected:typeof string"); + msg.split("\n")[3].strip.should.equal("Actual:typeof " ~ Type.stringof); + }); + + it("should throw a detailed error when the types match and they should not", { + auto msg = ({ + expect(value).to.not.be.instanceOf!Type; + }).should.throwException!TestException.msg; + + msg.split("\n")[0].should.equal(value.to!string ~ ` should not be instance of "` ~ Type.stringof ~ `". ` ~ value.to!string ~ " is instance of " ~ Type.stringof ~ "."); + msg.split("\n")[2].strip.should.equal("Expected:not typeof " ~ Type.stringof); + msg.split("\n")[3].strip.should.equal("Actual:typeof " ~ Type.stringof); + }); + }); + } +});