Sorry if this is a silly question, I am merely curious… I’m experimenting with writing a unit testing library for testing Defold game code and ran into a case that caused me a gray hair or two.
I have a main xunit.collection running the test script, which loads undertest.collection into a collectionproxy, then tries to do some assertion on it… it’s still early in development, so for now I am simply trying to assert(go.exists("/xunit_is_cool"), "Expected /xunit_is_cool to exist"). I quickly realized this doesn’t work…
According to these bits of documentation it looked like go.exists("undertest:/xunit_is_cool") should work:
It does not. Eventually I gave up and started trying random things with the debugger and discovered that I need to do go.exists("/tests#testproxy1/xunit_is_cool")…
This is not necessarily a problem, the test code looks quite reasonable:
case.GameTestCase_can_load_a_collection_under_test = function(self)
-- Given
local proxy = "/tests#testproxy1"
self.util.loadCollection(proxy)
self.gassert.receivedMessage(hash("proxy_loaded"))
self.util.initCollection(proxy)
-- When Then
self.gassert.gameObjectExists(proxy .. "/xunit_is_cool")
end
I might just need to think about how to document this difference if get far enough for this to turn into a real library, especially when I start adding sending messages into the collection under test or asserting if it received certain events.
The script running your unit test from xunit.collection (I suppose this is where you have the unit test script which calls go.exist) needs to use the full absolute path of the object to check.
What if you put a script on xunit_is_cool and do a print(msg.url()) to show the absolute URL of the game object?
Yeah, the way my library code works, each step is tried until it succeeds or we run out of time for the test case:
case.GameTestCase_can_load_a_collection_under_test = function(self)
-- Given
local proxy = "/tests#testproxy1"
self.util.loadCollection(proxy) -- step 1: load collection
self.gassert.receivedMessage(hash("proxy_loaded")) -- wait for proxy_loaded event
self.util.initCollection(proxy) -- step 2: send init & enable events
-- When Then
self.gassert.gameObjectExists("undertest:/xunit_is_cool") -- wait for assert to become true
end
EDIT: fix to show non-working assert above.
This runs 3 seconds and times out:
DEBUG:SCRIPT: Suite: Game Tests Suite
DEBUG:SCRIPT: GameTestCaseTests.GameTestCase_can_load_a_collection_under_test: FAIL (3.013s; 217 frames)
DEBUG:SCRIPT: Timed out, last error: main/xunit.script:194: Expected 'undertest:/xunit_is_cool' to exist
DEBUG:SCRIPT: 1 run, 1 failed
Which I think is enough time, because in the case with the working url it completes very quickly:
DEBUG:SCRIPT: Suite: Game Tests Suite
DEBUG:SCRIPT: GameTestCaseTests.GameTestCase_can_load_a_collection_under_test: OK (0.066s; 5 frames)
DEBUG:SCRIPT: 1 run, 0 failed
I had an idea to try to confirm that the collection:/id pattern in fact works for sending messages into the loaded collection, seems so:
case.GameTestCase_can_send_message_into_a_collection_under_test = function(self)
-- Given
local proxy = "/tests#testproxy1"
self.util.loadCollection(proxy)
self.gassert.receivedMessage(hash("proxy_loaded"))
self.util.initCollection(proxy)
-- When
self.util.sendMessage("undertest:/xunit_is_cool", "create_bullet", { position = vmath.vector3(5, 5, 0) })
-- Then
self.gassert.gameObjectExists(proxy .. "/bullets/instance0") -- works
-- self.gassert.gameObjectExists("undertest:" .. "/bullets/instance0") -- times out
end
DEBUG:SCRIPT: Created bullet: [/instance0]
DEBUG:SCRIPT: Suite: Game Tests Suite
DEBUG:SCRIPT: GameTestCaseTests.GameTestCase_can_send_message_into_a_collection_under_test: OK (0.081s; 6 frames)
DEBUG:SCRIPT: 1 run, 0 failed
Note: self.util.sendMessage(...) is just a wrapper for msg.post(...) to happen eventually after the other steps completed.
At this point I’m not sure if I can check for the existence of game objects inside a proxied collection… I’ll try to look around the engine code a bit to see if I can understand why / how to do it instead.
Remember than anything after # will refer to a component and will be ignored for the purpose of that function. You need to use the ‘socket’ of the loaded collection when checking your bullet instance. Something like this:
go.exists("foobar:/bullets/instance5")
This will check if the game object instance /bullets/instance5 exists in the loaded collection with socket/name foobar.
It seems to me that using the socket in a URL like you suggest, only works for msg.post(), not for go.exists(). Here is a short proof: DefoldUrls.zip (627.0 KB).
-- Main cannot see into subscene with go.exists:
DEBUG:SCRIPT: Main 'subscene:/child' exists? no
-- Subscene can see inside itself with go.exists:
DEBUG:SCRIPT: Subscene '/child' exists? yes
DEBUG:SCRIPT: Subscene 'subscene:/child' exists? yes
-- Subscene cannot see into main with go.exists:
DEBUG:SCRIPT: Subscene 'main:/main' exists? no
-- They can only communicate via msg.post with socket:
DEBUG:SCRIPT: Main 'do_you_exist' event received? yes
DEBUG:SCRIPT: Subscene 'do_you_exist' event received? yes
Question is if it’s a bug or just something to try to clarify in documentation?
I was wrong and should have checked the code. All other go.* functions will generate a Lua error if you try to interact with a game object from a different collection, but go.exists() will simply return false.
This is not documented anywhere and the least we could do is to add a note in the docs. The alternative is to throw a Lua error. OR actually allow the function to check in another collection.