VERSION 1.0 CLASS BEGIN MultiUse = -1 'True END Attribute VB_Name = "SpecExpectation" Attribute VB_GlobalNameSpace = False Attribute VB_Creatable = False Attribute VB_PredeclaredId = False Attribute VB_Exposed = True '' ' SpecExpectation v1.4.0 ' (c) Tim Hall - https://github.com/timhall/Excel-TDD ' ' Provides various tests that can be performed for a provided value ' ' @dependencies ' @author tim.hall.engr@gmail.com ' @license: MIT (http://www.opensource.org/licenses/mit-license.php) ' ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' Public Enum ExpectResult Pass Fail End Enum Public Actual As Variant Public Expected As Variant Public Result As ExpectResult Public FailureMessage As String ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' ' Public Methods ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' '' ' Check if the actual value is equal / not equal to the expected value ' ' @param {Variant} Expected ' --------------------------------------------- ' Public Sub ToEqual(Expected As Variant) Check IsEqual(Me.Actual, Expected), "to equal", Expected:=Expected End Sub Public Sub ToNotEqual(Expected As Variant) Check IsEqual(Me.Actual, Expected), "to not equal", Expected:=Expected, Inverse:=True End Sub Private Function IsEqual(Actual As Variant, Expected As Variant) As Variant If IsError(Actual) Or IsError(Expected) Then IsEqual = False ElseIf IsObject(Actual) Or IsObject(Expected) Then IsEqual = "Unsupported: Can't compare objects" ElseIf VarType(Actual) = vbDouble And VarType(Expected) = vbDouble Then ' It is inherently difficult/almost impossible to check equality of Double ' http://support.microsoft.com/kb/78113 ' ' Compare up to 15 significant figures ' -> Format as scientific notation with 15 significant figures and then compare strings IsEqual = IsCloseTo(Actual, Expected, 15) Else IsEqual = Actual = Expected End If End Function '' ' Check if the actual value is undefined / not undefined ' (Nothing, Empty, Null, or Missing) ' --------------------------------------------- ' Public Sub ToBeDefined() Debug.Print "Excel-TDD: DEPRECATED, ToBeDefined() has been deprecated in favor of ToNotBeUndefined and will be removed in Excel-TDD v2.0.0" Check IsUndefined(Me.Actual), "to be defined", Inverse:=True End Sub Public Sub ToBeUndefined() Check IsUndefined(Me.Actual), "to be undefined" End Sub Public Sub ToNotBeUndefined() Check IsUndefined(Me.Actual), "to not be undefined", Inverse:=True End Sub Private Function IsUndefined(Actual As Variant) As Variant IsUndefined = IsNothing(Actual) Or IsEmpty(Actual) Or IsNull(Actual) Or IsMissing(Actual) End Function '' ' Check if the actual value is nothing / not nothing ' --------------------------------------------- ' Public Sub ToBeNothing() Check IsNothing(Me.Actual), "to be nothing" End Sub Public Sub ToNotBeNothing() Check IsNothing(Me.Actual), "to not be nothing", Inverse:=True End Sub Private Function IsNothing(Actual As Variant) As Variant If IsObject(Actual) Then If Actual Is Nothing Then IsNothing = True Else IsNothing = False End If Else IsNothing = False End If End Function '' ' Check if the actual value is empty / not empty ' --------------------------------------------- ' Public Sub ToBeEmpty() Check IsEmpty(Me.Actual), "to be empty" End Sub Public Sub ToNotBeEmpty() Check IsEmpty(Me.Actual), "to not be empty", Inverse:=True End Sub '' ' Check if the actual value is null / not null ' --------------------------------------------- ' Public Sub ToBeNull() Check IsNull(Me.Actual), "to be null" End Sub Public Sub ToNotBeNull() Check IsNull(Me.Actual), "to not be null", Inverse:=True End Sub '' ' Check if the actual value is missing / not missing ' --------------------------------------------- ' Public Sub ToBeMissing() Check IsMissing(Me.Actual), "to be missing" End Sub Public Sub ToNotBeMissing() Check IsMissing(Me.Actual), "to not be missing", Inverse:=True End Sub '' ' Check if the actual value is less than the expected value ' ' @param {Variant} Expected ' --------------------------------------------- ' Public Sub ToBeLessThan(Expected As Variant) Check IsLT(Me.Actual, Expected), "to be less than", Expected:=Expected End Sub Public Sub ToBeLT(Expected As Variant) ToBeLessThan Expected End Sub Private Function IsLT(Actual As Variant, Expected As Variant) As Variant If IsError(Actual) Or IsError(Expected) Or Actual >= Expected Then IsLT = False Else IsLT = True End If End Function '' ' Check if the actual value is less than or equal to the expected value ' ' @param {Variant} Expected ' --------------------------------------------- ' Public Sub ToBeLessThanOrEqualTo(Expected As Variant) Check IsLTE(Me.Actual, Expected), "to be less than or equal to", Expected:=Expected End Sub Public Sub ToBeLTE(Expected As Variant) ToBeLessThanOrEqualTo Expected End Sub Private Function IsLTE(Actual As Variant, Expected As Variant) As Variant If IsError(Actual) Or IsError(Expected) Or Actual > Expected Then IsLTE = False Else IsLTE = True End If End Function '' ' Check if the actual value is greater than the expected value ' ' @param {Variant} Expected ' --------------------------------------------- ' Public Sub ToBeGreaterThan(Expected As Variant) Check IsGT(Me.Actual, Expected), "to be greater than", Expected:=Expected End Sub Public Sub ToBeGT(Expected As Variant) ToBeGreaterThan Expected End Sub Private Function IsGT(Actual As Variant, Expected As Variant) As Variant If IsError(Actual) Or IsError(Expected) Or Actual <= Expected Then IsGT = False Else IsGT = True End If End Function '' ' Check if the actual value is greater than or equal to the expected value ' ' @param {Variant} Expected ' --------------------------------------------- ' Public Sub ToBeGreaterThanOrEqualTo(Expected As Variant) Check IsGTE(Me.Actual, Expected), "to be greater than or equal to", Expected:=Expected End Sub Public Sub ToBeGTE(Expected As Variant) ToBeGreaterThanOrEqualTo Expected End Sub Private Function IsGTE(Actual As Variant, Expected As Variant) As Variant If IsError(Actual) Or IsError(Expected) Or Actual < Expected Then IsGTE = False Else IsGTE = True End If End Function '' ' Check if the actual value is close to the expected value ' ' @param {Variant} Expected ' @param {Integer} SignificantFigures (1-15) ' --------------------------------------------- ' Public Sub ToBeCloseTo(Expected As Variant, SignificantFigures As Integer) Check IsCloseTo(Me.Actual, Expected, SignificantFigures), "to be close to", Expected:=Expected End Sub Public Sub ToNotBeCloseTo(Expected As Variant, SignificantFigures As Integer) Check IsCloseTo(Me.Actual, Expected, SignificantFigures), "to be close to", Expected:=Expected, Inverse:=True End Sub Private Function IsCloseTo(Actual As Variant, Expected As Variant, SignificantFigures As Integer) As Variant Dim ActualAsString As String Dim ExpectedAsString As String If SignificantFigures < 1 Or SignificantFigures > 15 Then IsCloseTo = "ToBeCloseTo/ToNotBeClose to can only compare from 1 to 15 significant figures""" ElseIf Not IsError(Actual) And Not IsError(Expected) Then ' Convert values to scientific notation strings and then compare strings If Actual > 1 Then ActualAsString = VBA.Format$(Actual, VBA.Left$("0.00000000000000", SignificantFigures + 1) & "e+0") Else ActualAsString = VBA.Format$(Actual, VBA.Left$("0.00000000000000", SignificantFigures + 1) & "e-0") End If If Expected > 1 Then ExpectedAsString = VBA.Format$(Expected, VBA.Left$("0.00000000000000", SignificantFigures + 1) & "e+0") Else ExpectedAsString = VBA.Format$(Expected, VBA.Left$("0.00000000000000", SignificantFigures + 1) & "e-0") End If IsCloseTo = ActualAsString = ExpectedAsString End If End Function '' ' Check if the actual value contains the expected value ' Deprecated: Check if the actual value contains the expected value ' ' @param {Variant} Expected ' @param {Boolean} [MatchCase=True] *deprecated ' --------------------------------------------- ' Public Sub ToContain(Expected As Variant, Optional MatchCase As Boolean = True) If VarType(Me.Actual) = vbString Then Debug.Print "Excel-TDD: DEPRECATED ToContain has been changed to ToMatch in Excel-TDD v2.0.0" If MatchCase Then Check Matches(Me.Actual, Expected), "to match", Expected:=Expected Else Check Matches(VBA.UCase$(Me.Actual), VBA.UCase$(Expected)), "to match", Expected:=Expected End If Else Check Contains(Me.Actual, Expected), "to contain", Expected:=Expected End If End Sub Public Sub ToNotContain(Expected As Variant, Optional MatchCase As Boolean = True) If VarType(Me.Actual) = vbString Then Debug.Print "Excel-TDD: DEPRECATED ToNotContain has been changed to ToMatch in Excel-TDD v2.0.0" If MatchCase Then Check Matches(Me.Actual, Expected), "to not match", Expected:=Expected, Inverse:=True Else Check Matches(VBA.UCase$(Me.Actual), VBA.UCase$(Expected)), "to not match", Expected:=Expected, Inverse:=True End If Else Check Contains(Me.Actual, Expected), "to not contain", Expected:=Expected, Inverse:=True End If End Sub Private Function Contains(Actual As Variant, Expected As Variant) As Variant If Not IsArray(Actual) Then Contains = "Error: Actual needs to be an Array or Collection for ToContain/ToNotContain" Else Dim i As Integer If TypeOf Actual Is Collection Then For i = 1 To Actual.Count If Actual.Item(i) = Expected Then Contains = True Exit Function End If Next i Else For i = LBound(Actual) To UBound(Actual) If Actual(i) = Expected Then Contains = True Exit Function End If Next i End If End If End Function '' ' Check if the actual value matches the expected value ' (Only checks if the actual contains the expected string currently) ' ' @param {Variant} Expected ' --------------------------------------------- ' Public Sub ToMatch(Expected As Variant) Check Matches(Me.Actual, Expected), "to match", Expected:=Expected End Sub Public Sub ToNotMatch(Expected As Variant) Check Matches(Me.Actual, Expected), "to not match", Expected:=Expected, Inverse:=True End Sub Private Function Matches(Actual As Variant, Expected As Variant) As Variant If InStr(Actual, Expected) > 0 Then Matches = True Else Matches = False End If End Function '' ' Run custom matcher ' ' @example ' .Expect(100).RunMatcher "Module.ToBeWithin", "to be within", 90, 110 ' ' Module: ' Public Function ToBeWithin(Actual As Variant, Args As Variant) As Variant ' If UBound(Args) - LBound(Args) < 1 Then ' ' Return string for specific failure message ' ToBeWithin = "Need to pass in upper-bound to ToBeWithin" ' Else ' If Actual >= Args(0) And Actual <= Args(1) Then ' ' Return true for pass ' ToBeWithin = True ' Else ' ' Return false for fail or custom failure message ' ToBeWithin = False ' End If ' End If ' End Function ' ' @param {String} Name of function for matcher ' @param {String} Message ' @param {...} Arguments for custom matcher ' --------------------------------------------- ' Public Sub RunMatcher(Name As String, Message As String, ParamArray Arguments()) Dim Expected As String Dim i As Integer Dim HasArguments As Boolean HasArguments = UBound(Arguments) >= 0 For i = LBound(Arguments) To UBound(Arguments) If Expected = "" Then Expected = GetStringForValue(Arguments(i)) ElseIf i = UBound(Arguments) Then If (UBound(Arguments) > 1) Then Expected = Expected & ", and " & GetStringForValue(Arguments(i)) Else Expected = Expected & " and " & GetStringForValue(Arguments(i)) End If Else Expected = Expected & ", " & GetStringForValue(Arguments(i)) End If Next i If HasArguments Then Check Application.Run(Name, Me.Actual, Arguments), Message, Expected:=Expected Else Check Application.Run(Name, Me.Actual), Message End If End Sub ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' ' Internal Methods ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' Private Sub Check(Result As Variant, Message As String, Optional Expected As Variant, Optional Inverse As Boolean = False) If Not IsMissing(Expected) Then If IsObject(Expected) Then Set Me.Expected = Expected Else Me.Expected = Expected End If End If If VarType(Result) = vbString Then Fails CStr(Result) Else If Inverse Then Result = Not Result End If If Result Then Passes Else Fails CreateFailureMessage(Message, Expected) End If End If End Sub Private Sub Passes() Me.Result = ExpectResult.Pass End Sub Private Sub Fails(Message As String) Me.Result = ExpectResult.Fail Me.FailureMessage = Message End Sub Private Function CreateFailureMessage(Message As String, Optional Expected As Variant) As String CreateFailureMessage = "Expected " & GetStringForValue(Me.Actual) & " " & Message If Not IsMissing(Expected) Then CreateFailureMessage = CreateFailureMessage & " " & GetStringForValue(Expected) End If End Function Private Function GetStringForValue(Value As Variant) As String If IsObject(Value) Then If Value Is Nothing Then GetStringForValue = "(Nothing)" Else GetStringForValue = "(Object)" End If ElseIf IsArray(Value) Then GetStringForValue = "(Array)" ElseIf IsEmpty(Value) Then GetStringForValue = "(Empty)" ElseIf IsNull(Value) Then GetStringForValue = "(Null)" ElseIf IsMissing(Value) Then GetStringForValue = "(Missing)" Else GetStringForValue = CStr(Value) End If If GetStringForValue = "" Then GetStringForValue = "(Undefined)" End If End Function Private Function IsArray(Value As Variant) As Boolean If Not IsEmpty(Value) Then If IsObject(Value) Then If TypeOf Value Is Collection Then IsArray = True End If ElseIf VarType(Value) = vbArray Or VarType(Value) = 8204 Then ' VarType = 8204 seems to arise from Array(...) constructor IsArray = True End If End If End Function