-
Notifications
You must be signed in to change notification settings - Fork 7
Mocking
- Overview
- Example
- Getting a mocked object instead of an object reference
- Mocking a task variable
- Mocking table classes
- Mocking a method
- Mocking a method on a window object
- Expecting parameters
- Returning data
- Field references
- Call count
- Specific calls
- Copying calls
- Twice and Times
- Always returning the same value
- Explaining why you expect calls
- Adding context
- Resetting the mock
- Calling private methods
OmnisTAP has an integrated mocking tool, designed make creating Mock Objects easy. It supports:
- Expecting specific parameters
- Returning specific values
- Different expectations and returns for multiple calls to a method
- Expecting a specific number of calls to a method
- Mocking objects, table classes, and windows
- Setting field reference return
Consider this example object. $greet
returns a greeting message to a person based on their relationship to you.
Sample ogGreeter
object
ogGreeter {
$greet(pcName,pcRelationship) {
If pcRelationship="Friend"&left(pcName,4)="Maid"
Quit method $cinst.$_greetFriend(pcName,"Formal")
Else If pcRelationship="Friend"
Quit method $cinst.$_greetFriend(pcName,"Normal")
Else If pcRelationship="Enemy"
Quit method $cinst.$_greetEnemy(pcName)
End If
Quit method $cinst.$_nod()
}
$_greetFriend(pcName,pcDisposition) {...}
$_greetEnemy(pcName) {...}
$_nod() {...}
}
A unit test using mocking would look like this:
OmnisTAP for ogGreeter
using mocking
Do $cinst.$mock($objects.ogGreeter) Returns lorGreeter
Do $cinst.$when("greeting different people")
Do lorGreeter.$mock("$_greetFriend").$expect("Maid Marian","Formal").$return_char("M'lady").$because("we greet maids formally")
Do lorGreeter.$mock("$_greetFriend").$expect("Robin Hood","Normal").$return_char("Hail").$because("we greet friends informally")
Do lorGreeter.$mock("$_greetEnemy").$expect("Prince John").$return_char("En garde").$because("enemies will be challenged to a duel")
Do lorGreeter.$greet("Maid Marian","Friend") Returns lcGreeting
Do ioTAP.$is_char(lcGreeting,"M'lady","We greet a maid formally")
Do lorGreeter.$greet("Robin Hood","Friend") Returns lcGreeting
Do ioTAP.$is_char(lcGreeting,"Hail","We greet a non-maid normally")
Do lorGreeter.$greet("Prince John","Enemy") Returns lcGreeting
Do ioTAP.$is_char(lcGreeting,"En garde","We greet an enemy as an enemy")
Do lorGreeter.$assert()
Do $cinst.$when("greeting people we don't know")
Do lorGreeter.$reset()
Do lorGreeter.$mock("$_nod").$call(1).$return_char("'Sup.").$because("we don't know them")
Do lorGreeter.$mock("$_nod").$call(2).$return_char("Hey.").$because("we don't know them, but like to vary our greetings")
Do lorGreeter.$greet("A random peasant","NPC") Returns lcGreeting
Do ioTAP.$is_char(lcGreeting,"'Sup.","We greet the first unknown relationship properly")
Do lorGreeter.$greet("A barkeep","NPC") Returns lcGreeting
Do ioTAP.$is_char(lcGreeting,"Hey.","We greet the second unknown relationship properly")
Do lorGreeter.$assert()
Test output looks like this:
TAP output for the sample tests
1..13
# When greeting different people
ok 1 We greet a maid formally
ok 2 We greet a non-maid normally
ok 3 We greet an enemy as an enemy
ok 4 We call $_greetFriend twice because we greet friends informally
ok 5 pcName for call 1 to $_greetFriend is correct
ok 6 pcDisposition for call 1 to $_greetFriend is correct
ok 7 pcName for call 2 to $_greetFriend is correct
ok 8 pcDisposition for call 2 to $_greetFriend is correct
ok 9 We call $_greetEnemy once because enemies will be challenged to a duel
ok 10 pcName for call 1 to $_greetEnemy is correct
# When greeting people we don't know
ok 11 We greet the first unknown relationship properly
ok 12 We greet the second unknown relationship properly
ok 13 We call $_nod twice because we don't know them, but like to vary our greetings
Creating a mock object
Use $cinst.$mock()
on a subclass of ogTAPSuper
to create an instantiate a mock object.
$cinst.$mock([item reference to a class to mock]) Returns [instance of the mocked class]
If mocking an object, you will receive an object reference back from $mock()
. If mocking a window, report, menu, toolbar, etc. you will receive an item reference to the window. In both instances, $construct()
and $destruct()
will be automatically overridden.
When mocking a table class, use a slightly different syntax.
$cinst.$mock([item reference to a table class to mock],[field reference to a row or list to hold the mock])
This syntax is necessary to avoid copying a mocked instance to a new instance. OmnisTAP can instantiate the mocked class directly into the passed variable.
The mocked instance will be automatically destroyed at the end of the test.
Example
Do $cinst.$mock($tables.tgGreeter,lrGreeter)
By default mocking an object will give you an object reference to an instance of the mocked object. If, however, you need a plain object you can get one by calling $new([object])
on the object ref.
Do $cinst.$mock($objects.ogGreeter).$new(loGreeter)
Use this object exactly as you would the object instance. This is particularly handy for mocking utility objects.
Note that you need to pass the object variable by reference to $new()
. This allows the mocker to get a persistent reference to the object since we can avoid copying it to a returned variable.
Example
Do $cinst.$mock($objects.ogTextUtils).$new() Returns uoText
When needing to mock out a task variable such as toDatabaseServers, these are global variables shared amongst classes. So even if you need to access it from a mocked object/item reference, it would still need to be done in the current instance. Storing a variable automatically replaces it with a mock. You can also store primitives like booleans, numbers, or strings.
Example
Do $cinst.$store(toDatabaseServers)
Do toDatabaseServers.$mock("Your method").$expect/return([Your variables])
The mocked object has a $mock()
method that mock a specific method.
Do [mocked instance].$mock("[method name]")
Note: Omit the parentheses when mocking a method name. Normally the custom is to always add parentheses to a method, but the mocker needs the name of the method, not an invocation of it.
Example
Do lorGreeter.$mock("$_greetFriend")
If you attempt mock a method that does not exist, you will produce a test failure. Check the test message for details on what method and class you tried to mock that OmnisTAP couldn't find.
Mocking a method sets an expectation that it will not be called. You can customize the mocked method's behavior by chaining calls to $mock()
.
When mocking a window, you can mock a method that resides on an object. This can be a local method like $click()
, or if the object is a sub window/smart field, it can be a method on the class represented by the smart field.
Do [mocked instance].$mock("$objs.[objectname].[method name]")
Example
Do lirWindows.$mock("$objs.efDate.$redraw").$callcount(1).$because("we need to redraw the date after updating it")
It may be necessary to add the method dynamically before you can mock it. This is common with sub-windows that you are dynamically calculating their $classname
in the $construct
. If the method requires parameters this is a shortcut to have those be the same as the method you are needing to mock.
; First dynamically add the method to your mocked class
Do iirLoanPayment_1LoanPayment.$objs.ppTop.$objs.ppChurch.$objs.swChurch.$methods.$add("$setFilter") Returns lirMockMethod
; Next copy the method to be like the original method you are mocking
Calculate lirMockMethod as $windows.we_Church.$methods.//$setFilter//
If you are assigning the $classname
in the construct
you will need to dynamically add any methods you wish to mock for your test. This technique will allow you to keep your test a unit test if your sub window/smart field would normally execute SQL when instantiated.
Do iirChurch.$objs.swChurch.$methods.$add("$getAddressRow")
Do iirChurch.$mock("$objs.swChurch.$getAddressRow").$return_list(lrChurch)
A mocked method can expect parameters to be passed into it.
Do [mocked instance].$mock("[method name]").$expect([param 1],[param 2],...,[param N])
You must call $expect()
immediately after calling $mock()
on the method you want to expect. $mock()
will setup the $expect()
method to receive the same parameters as the method being mocked.
This expectation will be verified by calling $assert()
after testing the method that calls the mocked method. See below for information on asserting the mock method.
You can call $expect()
multiple times for methods that will be invoked more than once during a test. By default the mock will expect calls in the order that you call $expect()
, although this can be overridden using $call()
. See below for more information.
Example
Do lorGreeter.$mock("$_greetFriend").$expect("Maid Marian","Formal")
You can set the return from a mocked method. Due to Omnis' lack of a declared return type, you must use a return method specific to the type of data you want to return.
Do [mocked instance].$mock("[method name]").$return[_optional type]()
$return([boolean])
$return_binary([binary])
$return_boolean([boolean])
$return_char([character])
$return_datetime([date/time])
$return_itemref([item reference])
$return_list([list])
$return_number([number or integer, any precision])
$return_objectref([object reference])
$return_row([row])
Like $expect()
, you must call $return()
immediately after calling $mock()
on a method. This lets the mock object know for which method you want the return.
You can chain a $return()
to an $expect()
to return a specific value when a specific set of parameters is passed in. Or, you can simple call $return()
directly on the mocked method to ignore parameters and just set the return value. Subsequent calls to $return()
will override the return value for the current call, or you can use $call()
to specific a call number for the $return
.
Example
Do lorGreeter.$mock("$_nod").$return_char("'Sup.")
When using $expect()
on a method that takes field references, pass a variable in the test code for the reference. If this variable is populated, OmnisTAP will assert the value passed to this reference will match the value passed to $expect()
.
You can set the return value for a field reference. Use $expect()
on the mocked method and pass in the value for the field reference you'd like the mock to return.
Do [mocked instance].$mock("[method name]").$expect(lcFieldReference1Value,[parameter 1 expectation],llFieldReference2Value,...,l[Type]FieldReference[N]Value)
You can mix field references and parameters as the method dictates.
You can specify the mock should return a different value by accessing it through the $parameters()
method.
Do [mocked instance].$mock("[method name]").$parameter(pnFieldReferenceParameterNumber).$return[_type]([return value])
You can use any of the standard $return()
variants for a field reference.
Do llCandidateLines.$add()
Do llCandidateLines.$add()
Do llCandidateLines.$add()
Calculate llMatchedLines as llCandidateLines
Calculate llMatchedLines.2.$selected as kTrue
Calculate llMatchedLines.3.$selected as kTrue
Do iorMock.$mock("$_selectMatchingLines").$expect(llCandidateLines).$return_number(2)
Do iorMock.$mock("$_selectMatchingLines").$parameter(1).$return_list(llMatchedLines)
... run tests
Each time you set an expectation or return for a mocked method, you add an expectation for how many times that method will be called. If, however, you only care about this call count and don't want to expect parameters or set a return value, you can use $callcount()
.
Do [mocked instance].$mock("[method name]").$callcount([number of expected calls])
Using $callcount(0)
is an explicit way to say you don't expect a method to be called.
Example
Do lorGreeter.$mock("$goodbye").$callcount(0)
For $expect()
and $return()
you can specify the call to the method that you want to mock by using $call()
.
Do [mocked instance].$mock("[method name]").$call([method call number]).[$expect()|$return()]
Example
Do lorGreeter.$mock("$_nod").$call(2).$return_char("Hey.")
You can copy an expected call and its return to another call. This provides a convenient way to mock repeated calls to a method, or to copy all expectations but vary the return on one call.
Do [mocked instance].$mock("[method name]").$call([destination method call number]).$copy([source method call number])
Example
Do lorGreeter.$mock("$_nod").$expect("person").$return_char("Hey.")
Do lorGreeter.$mock("$_nod").$call(2).$copy(1).$return_char("Sup.")
OmnisTAP provides two convenience methods for copying mocks. If you expect a mocked method to be called twice, add $twice()
to the end of the mock.
Example
Do lorGreeter.$mock("$_nod").$expect("person").$return_char("Hey.").$twice()
This is equivalent to writing:
Do lorGreeter.$mock("$_nod").$expect("person").$return_char("Hey.")
Do lorGreeter.$mock("$_nod").$call(2).$copy(1)
If you need to copy a mock more than twice, use the $times([calls])
method. Pass the total number of times the mock should be expected:
Example
Do lorGreeter.$mock("$_nod").$expect("friend").$return_char("Hi!").$times(3)
This is equivalent to writing:
Do lorGreeter.$mock("$_nod").$expect("friend").$return_char("Hi!").$times(3)
Do lorGreeter.$mock("$_nod").$call(2).$copy(1)
Do lorGreeter.$mock("$_nod").$call(3).$copy(1)
Both $twice()
and $times([calls])
will copy the current call's expectation and return value. So this code:
Do lorGreeter.$mock("$_nod").$expect("person").$return_char("Hey.")
Do lorGreeter.$mock("$_nod").$call(2).$copy(1).$return_char("Sup.").$twice()
Is the same as this code:
Do lorGreeter.$mock("$_nod").$expect("person").$return_char("Hey.")
Do lorGreeter.$mock("$_nod").$call(2).$copy(1).$return_char("Sup.")
Do lorGreeter.$mock("$_nod").$call(3).$copy(2)
By default, OmnisTAP assumes every call to a mocked method will be expected in by your test code. This ensures the method is called no fewer and no more times than expected, thereby asserting correct behavior.
However, you may not care how many times a method is called, but instead need to mock the method to always return fixture data. This is especially valuable when testing looping or branching code that calls another method to get the application's state.
In this example, OmnisTAP provides the $always()
feature to return the same data no matter how many times the code is called.
Do [mocked instance].$mock("[method name"]).$return[_datatype]([return value]).$always()
When $always()
is used, asserting mocks does not evaluate expected calls to that method.
Example
Do $cinst.$when("Read-only")
Do iorMock.$mock("$isReadOnly").$return(kTrue).$always()
#Add tests for read-only conditions
Mocking makes it clear what you expect the code to do. But you can also note why it should have that behavior using $because()
.
Do [mocked instance].$mock("[method name"]).[$expect()|$callcount()].$because("reason")
Example
Do lorGreeter.$mock("$_nod").$callcount(1).$because("we nod to people we don't know")
Keep the first word of the reason lowercase to let the test messages be more readable.
While not technically part of the mocking utility, OmnisTAP lets you group related tests with a $when()
call.
Do $cinst.$when("context or scenario for the following tests")
Example
Do $cinst.$when("it's any given Sunday")
Do lorTV.$mock("$lineup").$expect("football").$because("it's the American way")
Do ioTAP.$is_char(lorTV.$getSchedule(),"next up: football","We display football next on the schedule")
Do $cinst.$assertMocks()
Like $because()
, use lowercase to start the context for $when()
.
Asserting the mock was called properly
OmnisTAP will track parameters that are passed to a mocked method and the number of times the method was called. After calling your test method, you can assert the expected behavior of the mocked methods using $assert()
.
Do [mocked instance].$assert()
If you have multiple mocked instances that you want to assert all at once, the TAP superclass offers a convenience method to do just that.
Do $cinst.$assertMocks()
Calling $assert()
will test expected parameters and call counts, then output the tests inline with your other assertions.
After asserting the mock was used properly, the mock will reset its expectations allowing you to return expected parameters and calls in subsequent tests.
Example
Do lorGreeter.$mock("$_isFriendly").$return(kFalse)
Do lorGreeter.$mock("$_greetFriendly).$callcount(0)
Do lorGreeter.$greet("Villain")
Do lorGreeter.$assert()
Do lorGreeter.$mock("$_isFriendly").$return(kTrue)
Do lorGreeter.$mock("$_greetFriendly).$callcount(1)
Do lorGreeter.$greet("Ally")
Do lorGreeter.$assert()
Subsequent calls to the mocked object will return the same mocked values, and the same expectations will be asserted. You can reset a single method or an entire mock using $reset()
.
Do [mocked instance].$reset()
Do [mocked instance].$mock("[method name]").$reset()
Example
Do $cinst.$when("it's any given Sunday")
Do lorTV.$mock("$lineup").$expect("football").$because("it's the American way")
Do ioTAP.$is_char(lorTV.$getSchedule("Sunday"),"next up: football","We display football next on the schedule")
Do $cinst.$assertMocks()
Do $cinst.$when("the tv is off")
Do lorTV.$reset()
Do ioTAP.$isclear(lorTV.$getSchedule("offline"),"The schedule is clear")
Do $cinst.$assertMocks()
While not technically mocking, OmnisTAP provides a convenience function to create a public forwarding method for calling a private method using $insertForwardingMethod
on a test class:
Do $cinst.$insertForwardingMethod([instance with the private method],[private method name])
This produces a new public method with the same name and parameters as the private method.
Example
Do $objects.oTree.$newref() Returns lorTreeRef
Do $cinst.$insertForwardingMethod(lorTreeRef,"isTree")
Do lorTreeRef.$isTree("spruce", "blue") Return lbActualValue
; Do some tests on the lbActualValue