Skip to content

Commit

Permalink
- updates mocks so that the expected args now expect all args to be …
Browse files Browse the repository at this point in the history
…equal

 - new m.invalidValue can specify an expected arg should be invalid
 - new m.ingnoreValue can specify to not check an arg
 - adds flag to allow creation of fake method when real method is not found
  • Loading branch information
George Cook committed Oct 2, 2018
1 parent 5f70eb2 commit ba341fe
Show file tree
Hide file tree
Showing 18 changed files with 449 additions and 366 deletions.
4 changes: 2 additions & 2 deletions apiDocs/Rooibos.brs.html

Large diffs are not rendered by default.

119 changes: 60 additions & 59 deletions apiDocs/Rooibos_BaseTestSuite.brs.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions apiDocs/Rooibos_CommonUtils.brs.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions apiDocs/Rooibos_ItemGenerator.brs.html

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions apiDocs/Rooibos_TestRunner.brs.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions apiDocs/index.html

Large diffs are not rendered by default.

376 changes: 146 additions & 230 deletions apiDocs/module-BaseTestSuite.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions apiDocs/module-CommonUtils.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions apiDocs/module-ItemGenerator.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions apiDocs/module-TestRunner.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions apiDocs/module-rooibosh.html

Large diffs are not rendered by default.

49 changes: 48 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ We create mocks by using the methods:

### Asserting mocks
Mocks are asserted by invoking `m.AssertMocks()`
As a convenience, Rooibos will automatically assert any mocks for you when your test finishes executing. This saves you from having to manually add this line at the end of your code.
***As a convenience, Rooibos will automatically assert any mocks for you when your test finishes executing***. This saves you from having to manually add this line at the end of your code.

When a mock fails, Rooibos will report to you what caused the failure. The possible reasons are:

Expand Down Expand Up @@ -590,6 +590,53 @@ detailsVM.LoadDetails()
? excecuteMock.invocations
```

#### Specifying expected invocation arguments
You can save yourself a lot of time, and really think about and kick the tyres of your code, by defining the arguments you expect a function to be invoked with. This is done by passing in an array of the expected invocation arguments via the expectedArgs param. You may also really not care about the args, in which case you can set that value to `invalid` and the call to `m.AssertMocks()` will skip checking the invoked args.

Up to 9 arguments are supported.


####Specifying an expected value of invalid, with m.invalidValue

If you specify the invoked args, then by default, rooibos will check that the invoked args match the arguments you specify. You can expect any value for an arg, or use the special `m.invalidValue` to indicate you expect the argument to be invalid. This is the default value.

So for example, if one had the following mock

```m.expectOnce(myObj, "myMethod", ["a", "b"], true)```

and `myMethod` was invoked with `["a", "b", "c"]`, this would be a mock failure, because the 3rd argument was expected to be invalid, by default.

In that case, the following mock definition would satisfy the assertion:

```m.expectOnce(myObj, "myMethod", ["a", "b", "c"], true)```


####Skipping value assertion with m.ignoreValue

If you only care about some arguments, then you can use `m.ignoreValue` to instruct rooibos to not assert equality for the arguments you've ignored.

In the above example, the assertion will be satisfied with a mock confiugred thusly:

```m.expectOnce(myObj, "myMethod", ["a", "b", m.ignoreValue], true)```

This will pass when `myMethod` is invoked with args: `["a", "b", "c"]`, as would the following mock definition:

```m.expectOnce(myObj, "myMethod", [m.ignoreValue, "b", "c"], true)```


###Allowing mocking of non-existent functions
As a sanity check, rooibos will expect a method to exist on an object before allowing it to be stubbed or mocked. If it is not, it will fail log an error and lead to a failing mock assertion.

This behaviour can be disabled by passing in the last argument of the mock/stub/fake function (`allowNonExistingMethods`) as `true`. In this case, rooibos will still log a warning; but you will be allowed to create the fake method.

This is handy for quickly creating mock dependencies with simple _pobos_ (plain old brightscript objects). e.g.

```
videoService = {}
m.expectOnce (videoService, "getVideos", invalid, someJson, true)
```


###Mock limitations
Please note, mocks DO NOT work with globally scoped methods (i.e. subs and functions which are not assigned to an associative array). E.g. if you have a method, which is not accessed via `m.SomeMethod`, or `someObject.SomeMethod`, then you cannot mock it. This is a long term limitation. If you are hitting this problem, I suggest it's likely a code-smell anyhow, and you might consider using a pattern such as MVVM, which will better allow you to separate view and business logic.

Expand Down
22 changes: 21 additions & 1 deletion samples/Overview/source/VideoModule.brs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ function VideoModule(constants, httpService)

'public api
getVideos: getVideos_
getVideosRealExample: getVideosRealExample_
}
end function

Expand All @@ -25,4 +26,23 @@ function getVideos_(videoType) as Object
videos.push({id:"video_" + stri(i).trim(), "type":videoType})
end for
return videos
end function
end function


function getVideosRealExample_(videoType) as Object
videos = []

'result = m.httpService_.getJson("https://myVideos.com/videos/", videoType)
result = invalid
if (type(result) = "roAssociativeArray")
for each video in result
videos.push(video)
end for
end if

return videos
end function




4 changes: 2 additions & 2 deletions samples/Overview/source/buildinfo.brs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Function BuildDate()
return "Oct 1 2018 18:44:37"
return "Oct 2 2018 21:08:46"
End Function
Function BuildCommit()
return "8eabc30"
return "5f70eb2"
End Function
53 changes: 41 additions & 12 deletions samples/Overview/source/rooibos.cat.brs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ end function
function BaseTestSuite() as Object
this = {}
this.Name = "BaseTestSuite"
this.invalidValue = "#ROIBOS#INVALID_VALUE" ' special value used in mock arguments
this.ignoreValue = "#ROIBOS#IGNORE_VALUE" ' special value used in mock arguments
this.TestCases = []
this.AddTest = RBS_BTS_AddTest
this.CreateTest = RBS_BTS_CreateTest
Expand Down Expand Up @@ -866,7 +868,7 @@ Function RBS_BTS_AssertAAContainsSubset(array as dynamic, subset as dynamic, ign
m.currentResult.AddResult("")
return m.GetLegacyCompatibleReturnValue(true)
End Function
function RBS_BTS_Stub(target, methodName, returnValue = invalid) as object
function RBS_BTS_Stub(target, methodName, returnValue = invalid, allowNonExistingMethods = false) as object
if (type(target) <> "roAssociativeArray")
m.Fail("could not create Stub provided target was null")
return {}
Expand All @@ -883,24 +885,27 @@ function RBS_BTS_Stub(target, methodName, returnValue = invalid) as object
id = stri(m.__stubId).trim()
fake = m.CreateFake(id, target, methodName, 1, invalid, returnValue)
m.stubs[id] = fake
if (type(target[methodName]) = "Function" or type(target[methodName]) = "roFunction")
if (type(target[methodName]) = "Function" or type(target[methodName]) = "roFunction" or allowNonExistingMethods)
target[methodName] = m["StubCallback" + id]
target.__stubs = m.stubs
if (allowNonExistingMethods)
? "WARNING - stubbing call " ; methodName; " which did not exist on target object"
end if
else
? "ERROR - could not create Stub : method not found "; target ; "." ; methodName
end if
return fake
end function
function RBS_BTS_ExpectOnce(target, methodName, expectedArgs = invalid, returnValue = invalid) as object
return m.Mock(target, methodName, 1, expectedArgs, returnValue)
function RBS_BTS_ExpectOnce(target, methodName, expectedArgs = invalid, returnValue = invalid, allowNonExistingMethods = false) as object
return m.Mock(target, methodName, 1, expectedArgs, returnValue, allowNonExistingMethods)
end function
function RBS_BTS_ExpectNone(target, methodName, expectedArgs = invalid, returnValue = invalid) as object
return m.Mock(target, methodName, 0, expectedArgs, returnValue)
function RBS_BTS_ExpectNone(target, methodName, expectedArgs = invalid, returnValue = invalid, allowNonExistingMethods = false) as object
return m.Mock(target, methodName, 0, expectedArgs, returnValue, allowNonExistingMethods)
end function
function RBS_BTS_Expect(target, methodName, expectedInvocations = 1, expectedArgs = invalid, returnValue = invalid) as object
return m.Mock(target, methodName, expectedInvocations, expectedArgs, returnValue)
function RBS_BTS_Expect(target, methodName, expectedInvocations = 1, expectedArgs = invalid, returnValue = invalid, allowNonExistingMethods = false) as object
return m.Mock(target, methodName, expectedInvocations, expectedArgs, returnValue, allowNonExistingMethods)
end function
function RBS_BTS_Mock(target, methodName, expectedInvocations = 1, expectedArgs = invalid, returnValue = invalid) as object
function RBS_BTS_Mock(target, methodName, expectedInvocations = 1, expectedArgs = invalid, returnValue = invalid, allowNonExistingMethods = false) as object
if (type(target) <> "roAssociativeArray")
m.Fail("could not create Stub provided target was null")
return {}
Expand All @@ -918,15 +923,32 @@ function RBS_BTS_Mock(target, methodName, expectedInvocations = 1, expectedArgs
id = stri(m.__mockId).trim()
fake = m.CreateFake(id, target, methodName, expectedInvocations, expectedArgs, returnValue)
m.mocks[id] = fake 'this will bind it to m
if (type(target[methodName]) = "Function" or type(target[methodName]) = "roFunction")
if (type(target[methodName]) = "Function" or type(target[methodName]) = "roFunction" or allowNonExistingMethods)
target[methodName] = m["MockCallback" + id]
target.__mocks = m.mocks
if (allowNonExistingMethods)
? "WARNING - mocking call " ; methodName; " which did not exist on target object"
end if
else
? "ERROR - could not create Mock : method not found "; target ; "." ; methodName
end if
return fake
end function
function RBS_BTS_CreateFake(id, target, methodName, expectedInvocations = 1, expectedArgs =invalid, returnValue=invalid ) as object
expectedArgsValues = []
hasArgs = expectedArgs <> invalid
if (hasArgs)
defaultValue = m.invalidValue
else
defaultValue = m.ignoreValue
end if
for i = 0 to 9
if (hasArgs and expectedArgs.count() > i)
expectedArgsValues.push(expectedArgs[i])
else
expectedArgsValues.push(defaultValue)
end if
end for
fake = {
id : id,
target: target,
Expand All @@ -935,7 +957,7 @@ function RBS_BTS_CreateFake(id, target, methodName, expectedInvocations = 1, exp
isCalled: false,
invocations: 0,
invokedArgs: [invalid, invalid, invalid, invalid, invalid, invalid, invalid, invalid, invalid],
expectedArgs: expectedArgs,
expectedArgs: expectedArgsValues,
expectedInvocations: expectedInvocations,
callback: function (arg1=invalid, arg2=invalid, arg3=invalid, arg4=invalid, arg5=invalid, arg6=invalid, arg7=invalid, arg8=invalid, arg9 =invalid)as dynamic
? "FAKE CALLBACK CALLED FOR " ; m.methodName
Expand Down Expand Up @@ -964,7 +986,14 @@ function RBS_BTS_AssertMocks() as void
for i = 0 to mock.expectedargs.count() -1
value = mock.invokedArgs[i]
expected = mock.expectedargs[i]
if (not m.eqValues(value,expected))
didNotExpectArg = expected = m.invalidValue
if (didNotExpectArg)
expected = invalid
end if
if (not expected = m.ignoreValue and not m.eqValues(value,expected))
if (expected = invalid)
expected = "[INVALID]"
end if
m.MockFail(methodName, "Expected arg #" + stri(i).trim() + " to be '" + RBS_CMN_AsString(expected) + "' got '" + RBS_CMN_AsString(value) + "')")
return
end if
Expand Down
15 changes: 15 additions & 0 deletions samples/Overview/source/tests/VideoModuleTests.brs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ function VMT__constructor_basic() as void
m.AssertEqual(m.module.httpService_, m.httpService)
end function


'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
'@It tests getVideos
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Expand All @@ -40,3 +41,17 @@ function VMT__getVideos_basic(expectedCount, videoType, videoIds) as void
m.AssertArrayContainsAAs(videos, expectedIds)
end function

'@Only
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
'@It tests getVideosRealExample
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

'@Test basic test
function VMT__getVideosRealExample_basic() as void
returnJson = {}
m.stub(m.httpService, "getJson", returnJson, true)

videos = m.module.getVideosRealExample("mp4")
end function


53 changes: 41 additions & 12 deletions source/rooibos.cat.brs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ end function
function BaseTestSuite() as Object
this = {}
this.Name = "BaseTestSuite"
this.invalidValue = "#ROIBOS#INVALID_VALUE" ' special value used in mock arguments
this.ignoreValue = "#ROIBOS#IGNORE_VALUE" ' special value used in mock arguments
this.TestCases = []
this.AddTest = RBS_BTS_AddTest
this.CreateTest = RBS_BTS_CreateTest
Expand Down Expand Up @@ -866,7 +868,7 @@ Function RBS_BTS_AssertAAContainsSubset(array as dynamic, subset as dynamic, ign
m.currentResult.AddResult("")
return m.GetLegacyCompatibleReturnValue(true)
End Function
function RBS_BTS_Stub(target, methodName, returnValue = invalid) as object
function RBS_BTS_Stub(target, methodName, returnValue = invalid, allowNonExistingMethods = false) as object
if (type(target) <> "roAssociativeArray")
m.Fail("could not create Stub provided target was null")
return {}
Expand All @@ -883,24 +885,27 @@ function RBS_BTS_Stub(target, methodName, returnValue = invalid) as object
id = stri(m.__stubId).trim()
fake = m.CreateFake(id, target, methodName, 1, invalid, returnValue)
m.stubs[id] = fake
if (type(target[methodName]) = "Function" or type(target[methodName]) = "roFunction")
if (type(target[methodName]) = "Function" or type(target[methodName]) = "roFunction" or allowNonExistingMethods)
target[methodName] = m["StubCallback" + id]
target.__stubs = m.stubs
if (allowNonExistingMethods)
? "WARNING - stubbing call " ; methodName; " which did not exist on target object"
end if
else
? "ERROR - could not create Stub : method not found "; target ; "." ; methodName
end if
return fake
end function
function RBS_BTS_ExpectOnce(target, methodName, expectedArgs = invalid, returnValue = invalid) as object
return m.Mock(target, methodName, 1, expectedArgs, returnValue)
function RBS_BTS_ExpectOnce(target, methodName, expectedArgs = invalid, returnValue = invalid, allowNonExistingMethods = false) as object
return m.Mock(target, methodName, 1, expectedArgs, returnValue, allowNonExistingMethods)
end function
function RBS_BTS_ExpectNone(target, methodName, expectedArgs = invalid, returnValue = invalid) as object
return m.Mock(target, methodName, 0, expectedArgs, returnValue)
function RBS_BTS_ExpectNone(target, methodName, expectedArgs = invalid, returnValue = invalid, allowNonExistingMethods = false) as object
return m.Mock(target, methodName, 0, expectedArgs, returnValue, allowNonExistingMethods)
end function
function RBS_BTS_Expect(target, methodName, expectedInvocations = 1, expectedArgs = invalid, returnValue = invalid) as object
return m.Mock(target, methodName, expectedInvocations, expectedArgs, returnValue)
function RBS_BTS_Expect(target, methodName, expectedInvocations = 1, expectedArgs = invalid, returnValue = invalid, allowNonExistingMethods = false) as object
return m.Mock(target, methodName, expectedInvocations, expectedArgs, returnValue, allowNonExistingMethods)
end function
function RBS_BTS_Mock(target, methodName, expectedInvocations = 1, expectedArgs = invalid, returnValue = invalid) as object
function RBS_BTS_Mock(target, methodName, expectedInvocations = 1, expectedArgs = invalid, returnValue = invalid, allowNonExistingMethods = false) as object
if (type(target) <> "roAssociativeArray")
m.Fail("could not create Stub provided target was null")
return {}
Expand All @@ -918,15 +923,32 @@ function RBS_BTS_Mock(target, methodName, expectedInvocations = 1, expectedArgs
id = stri(m.__mockId).trim()
fake = m.CreateFake(id, target, methodName, expectedInvocations, expectedArgs, returnValue)
m.mocks[id] = fake 'this will bind it to m
if (type(target[methodName]) = "Function" or type(target[methodName]) = "roFunction")
if (type(target[methodName]) = "Function" or type(target[methodName]) = "roFunction" or allowNonExistingMethods)
target[methodName] = m["MockCallback" + id]
target.__mocks = m.mocks
if (allowNonExistingMethods)
? "WARNING - mocking call " ; methodName; " which did not exist on target object"
end if
else
? "ERROR - could not create Mock : method not found "; target ; "." ; methodName
end if
return fake
end function
function RBS_BTS_CreateFake(id, target, methodName, expectedInvocations = 1, expectedArgs =invalid, returnValue=invalid ) as object
expectedArgsValues = []
hasArgs = expectedArgs <> invalid
if (hasArgs)
defaultValue = m.invalidValue
else
defaultValue = m.ignoreValue
end if
for i = 0 to 9
if (hasArgs and expectedArgs.count() > i)
expectedArgsValues.push(expectedArgs[i])
else
expectedArgsValues.push(defaultValue)
end if
end for
fake = {
id : id,
target: target,
Expand All @@ -935,7 +957,7 @@ function RBS_BTS_CreateFake(id, target, methodName, expectedInvocations = 1, exp
isCalled: false,
invocations: 0,
invokedArgs: [invalid, invalid, invalid, invalid, invalid, invalid, invalid, invalid, invalid],
expectedArgs: expectedArgs,
expectedArgs: expectedArgsValues,
expectedInvocations: expectedInvocations,
callback: function (arg1=invalid, arg2=invalid, arg3=invalid, arg4=invalid, arg5=invalid, arg6=invalid, arg7=invalid, arg8=invalid, arg9 =invalid)as dynamic
? "FAKE CALLBACK CALLED FOR " ; m.methodName
Expand Down Expand Up @@ -964,7 +986,14 @@ function RBS_BTS_AssertMocks() as void
for i = 0 to mock.expectedargs.count() -1
value = mock.invokedArgs[i]
expected = mock.expectedargs[i]
if (not m.eqValues(value,expected))
didNotExpectArg = expected = m.invalidValue
if (didNotExpectArg)
expected = invalid
end if
if (not expected = m.ignoreValue and not m.eqValues(value,expected))
if (expected = invalid)
expected = "[INVALID]"
end if
m.MockFail(methodName, "Expected arg #" + stri(i).trim() + " to be '" + RBS_CMN_AsString(expected) + "' got '" + RBS_CMN_AsString(value) + "')")
return
end if
Expand Down
Loading

0 comments on commit ba341fe

Please sign in to comment.