biocomp / hubitat_ci

Unit testing framework for Hubitat scripts
Apache License 2.0
9 stars 5 forks source link

Commands like zwave.versionV1.versionGet() don't work #38

Open cmorse opened 4 years ago

cmorse commented 4 years ago

I've been trying to get commands like those inside of updated() in the example below to work. It always fails on the zwave.versionV1.versionGet() with a "Cannot get property 'versionV1' on null object". I tried mocking DeviceExecutor.getZwave(), but I just got casting exceptions. Based on the method dumps of hubitat.zwave.Zwave and hubitat.zwave.commandclasses.VersionV1, I think the Zwave class may need to be expanded.

metadata {
    definition(name: 'Test', namespace: 'test', author: 'test') {
        capability 'Actuator'
    }
    preferences {}
}

def installed() {}

def updated() {
    def cmds = []

    cmds << zwave.versionV1.versionGet().format()

    sendHubCommand(new hubitat.device.HubMultiAction(cmds, hubitat.device.Protocol.ZWAVE))
}

here is a dump of hubitat.zwave.Zwave

Platform version: 2.2.2.129

class hubitat.zwave.Zwave:
Methods:[
  public static java.lang.Class hubitat.zwave.Zwave.getAlarmV1()
  public static java.lang.Class hubitat.zwave.Zwave.getAlarmV2()
  public static java.lang.Class hubitat.zwave.Zwave.getApplicationCapabilityV1()
  public static java.lang.Class hubitat.zwave.Zwave.getApplicationStatusV1()
  public static java.lang.Class hubitat.zwave.Zwave.getAssociationCommandConfigurationV1()
  public static java.lang.Class hubitat.zwave.Zwave.getAssociationGrpInfoV1()
  public static java.lang.Class hubitat.zwave.Zwave.getAssociationGrpInfoV2()
  public static java.lang.Class hubitat.zwave.Zwave.getAssociationGrpInfoV3()
  public static java.lang.Class hubitat.zwave.Zwave.getAssociationV1()
  public static java.lang.Class hubitat.zwave.Zwave.getAssociationV2()
  public static java.lang.Class hubitat.zwave.Zwave.getAssociationV3()
  public static java.lang.Class hubitat.zwave.Zwave.getAvContentDirectoryMdV1()
  public static java.lang.Class hubitat.zwave.Zwave.getAvContentSearchMdV1()
  public static java.lang.Class hubitat.zwave.Zwave.getAvRendererStatusV1()
  public static java.lang.Class hubitat.zwave.Zwave.getAvTaggingMdV1()
  public static java.lang.Class hubitat.zwave.Zwave.getBarrierOperatorV1()
  public static java.lang.Class hubitat.zwave.Zwave.getBasicTariffInfoV1()
  public static java.lang.Class hubitat.zwave.Zwave.getBasicV1()
  public static java.lang.Class hubitat.zwave.Zwave.getBasicV2()
  public static java.lang.Class hubitat.zwave.Zwave.getBasicWindowCoveringV1()
  public static java.lang.Class hubitat.zwave.Zwave.getBatteryV1()
  public static java.lang.Class hubitat.zwave.Zwave.getCentralSceneV1()
  public static java.lang.Class hubitat.zwave.Zwave.getCentralSceneV2()
  public static java.lang.Class hubitat.zwave.Zwave.getCentralSceneV3()
  public static java.lang.Class hubitat.zwave.Zwave.getChimneyFanV1()
  public static java.lang.Class hubitat.zwave.Zwave.getClimateControlScheduleV1()
  public static java.lang.Class hubitat.zwave.Zwave.getClockV1()
  public static hubitat.zwave.Command hubitat.zwave.Zwave.getCommand(byte,byte,byte[])
  public static hubitat.zwave.Command hubitat.zwave.Zwave.getCommand(java.lang.Short,java.lang.Short,java.util.List)
  public static hubitat.zwave.Command hubitat.zwave.Zwave.getCommand(byte,byte,byte[],java.lang.Integer)
  public static hubitat.zwave.Command hubitat.zwave.Zwave.getCommand(java.lang.Short,java.lang.Short,java.util.List,java.lang.Integer)
  public static java.lang.Class hubitat.zwave.Zwave.getConfigurationV1()
  public static java.lang.Class hubitat.zwave.Zwave.getConfigurationV2()
  public static java.lang.Class hubitat.zwave.Zwave.getControllerReplicationV1()
  public static java.lang.Class hubitat.zwave.Zwave.getCrc16EncapV1()
  public static java.lang.Class hubitat.zwave.Zwave.getDcpConfigV1()
  public static java.lang.Class hubitat.zwave.Zwave.getDcpMonitorV1()
  public static java.lang.Class hubitat.zwave.Zwave.getDeviceResetLocallyV1()
  public static java.lang.Class hubitat.zwave.Zwave.getDoorLockLoggingV1()
  public static java.lang.Class hubitat.zwave.Zwave.getDoorLockV1()
  public static java.lang.Class hubitat.zwave.Zwave.getEnergyProductionV1()
  public static java.lang.Class hubitat.zwave.Zwave.getFirmwareUpdateMdV1()
  public static java.lang.Class hubitat.zwave.Zwave.getFirmwareUpdateMdV2()
  public static java.lang.Class hubitat.zwave.Zwave.getFirmwareUpdateMdV3()
  public static java.lang.Class hubitat.zwave.Zwave.getFirmwareUpdateMdV4()
  public static java.lang.Class hubitat.zwave.Zwave.getFirmwareUpdateMdV5()
  public static java.lang.Class hubitat.zwave.Zwave.getFirmwareUpdateMdV6()
  public static java.lang.Class hubitat.zwave.Zwave.getGeographicLocationV1()
  public static java.lang.Class hubitat.zwave.Zwave.getGroupingNameV1()
  public static java.lang.Class hubitat.zwave.Zwave.getHailV1()
  public static java.lang.Class hubitat.zwave.Zwave.getHrvControlV1()
  public static java.lang.Class hubitat.zwave.Zwave.getHrvStatusV1()
  public static java.lang.Class hubitat.zwave.Zwave.getIndicatorV1()
  public static java.lang.Class hubitat.zwave.Zwave.getIndicatorV2()
  public static java.lang.Class hubitat.zwave.Zwave.getIndicatorV3()
  public static java.lang.Class hubitat.zwave.Zwave.getIpConfigurationV1()
  public static java.lang.Class hubitat.zwave.Zwave.getLanguageV1()
  public static java.lang.Class hubitat.zwave.Zwave.getLockV1()
  public static java.lang.Class hubitat.zwave.Zwave.getManufacturerSpecificV1()
  public static java.lang.Class hubitat.zwave.Zwave.getManufacturerSpecificV2()
  public static java.lang.Class hubitat.zwave.Zwave.getMeterPulseV1()
  public static java.lang.Class hubitat.zwave.Zwave.getMeterTblConfigV1()
  public static java.lang.Class hubitat.zwave.Zwave.getMeterTblMonitorV1()
  public static java.lang.Class hubitat.zwave.Zwave.getMeterTblMonitorV2()
  public static java.lang.Class hubitat.zwave.Zwave.getMeterTblPushV1()
  public static java.lang.Class hubitat.zwave.Zwave.getMeterV1()
  public static java.lang.Class hubitat.zwave.Zwave.getMeterV2()
  public static java.lang.Class hubitat.zwave.Zwave.getMeterV3()
  public static java.lang.Class hubitat.zwave.Zwave.getMeterV4()
  public static java.lang.Class hubitat.zwave.Zwave.getMeterV5()
  public static java.lang.Class hubitat.zwave.Zwave.getMtpWindowCoveringV1()
  public static java.lang.Class hubitat.zwave.Zwave.getMultiChannelAssociationV2()
  public static java.lang.Class hubitat.zwave.Zwave.getMultiChannelAssociationV3()
  public static java.lang.Class hubitat.zwave.Zwave.getMultiChannelAssociationV4()
  public static java.lang.Class hubitat.zwave.Zwave.getMultiChannelV3()
  public static java.lang.Class hubitat.zwave.Zwave.getMultiChannelV4()
  public static java.lang.Class hubitat.zwave.Zwave.getMultiCmdV1()
  public static java.lang.Class hubitat.zwave.Zwave.getMultiInstanceAssociationV1()
  public static java.lang.Class hubitat.zwave.Zwave.getMultiInstanceV1()
  public static java.lang.Class hubitat.zwave.Zwave.getNodeNamingV1()
  public static java.lang.Class hubitat.zwave.Zwave.getNotificationV3()
  public static java.lang.Class hubitat.zwave.Zwave.getNotificationV4()
  public static java.lang.Class hubitat.zwave.Zwave.getNotificationV5()
  public static java.lang.Class hubitat.zwave.Zwave.getNotificationV6()
  public static java.lang.Class hubitat.zwave.Zwave.getNotificationV7()
  public static java.lang.Class hubitat.zwave.Zwave.getNotificationV8()
  public static java.lang.Class hubitat.zwave.Zwave.getPowerlevelV1()
  public static java.lang.Class hubitat.zwave.Zwave.getPrepaymentEncapsulationV1()
  public static java.lang.Class hubitat.zwave.Zwave.getPrepaymentV1()
  public static java.lang.Class hubitat.zwave.Zwave.getProprietaryV1()
  public static java.lang.Class hubitat.zwave.Zwave.getProtectionV1()
  public static java.lang.Class hubitat.zwave.Zwave.getProtectionV2()
  public static java.lang.Class hubitat.zwave.Zwave.getRateTblConfigV1()
  public static java.lang.Class hubitat.zwave.Zwave.getRateTblMonitorV1()
  public static java.lang.Class hubitat.zwave.Zwave.getRemoteAssociationActivateV1()
  public static java.lang.Class hubitat.zwave.Zwave.getRemoteAssociationV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSceneActivationV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSceneActuatorConfV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSceneControllerConfV1()
  public static java.lang.Class hubitat.zwave.Zwave.getScheduleEntryLockV1()
  public static java.lang.Class hubitat.zwave.Zwave.getScheduleEntryLockV2()
  public static java.lang.Class hubitat.zwave.Zwave.getScheduleEntryLockV3()
  public static java.lang.Class hubitat.zwave.Zwave.getScheduleV1()
  public static java.lang.Class hubitat.zwave.Zwave.getScreenAttributesV1()
  public static java.lang.Class hubitat.zwave.Zwave.getScreenAttributesV2()
  public static java.lang.Class hubitat.zwave.Zwave.getScreenMdV1()
  public static java.lang.Class hubitat.zwave.Zwave.getScreenMdV2()
  public static java.lang.Class hubitat.zwave.Zwave.getSecurity2V1()
  public static java.lang.Class hubitat.zwave.Zwave.getSecurityPanelModeV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSecurityPanelZoneSensorV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSecurityPanelZoneV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSecurityV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSensorAlarmV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSensorBinaryV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSensorBinaryV2()
  public static java.lang.Class hubitat.zwave.Zwave.getSensorConfigurationV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSensorMultilevelV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSensorMultilevelV10()
  public static java.lang.Class hubitat.zwave.Zwave.getSensorMultilevelV11()
  public static java.lang.Class hubitat.zwave.Zwave.getSensorMultilevelV2()
  public static java.lang.Class hubitat.zwave.Zwave.getSensorMultilevelV3()
  public static java.lang.Class hubitat.zwave.Zwave.getSensorMultilevelV4()
  public static java.lang.Class hubitat.zwave.Zwave.getSensorMultilevelV5()
  public static java.lang.Class hubitat.zwave.Zwave.getSensorMultilevelV6()
  public static java.lang.Class hubitat.zwave.Zwave.getSensorMultilevelV7()
  public static java.lang.Class hubitat.zwave.Zwave.getSensorMultilevelV8()
  public static java.lang.Class hubitat.zwave.Zwave.getSensorMultilevelV9()
  public static java.lang.Class hubitat.zwave.Zwave.getSilenceAlarmV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSimpleAvControlV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSupervisionV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSwitchAllV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSwitchBinaryV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSwitchBinaryV2()
  public static java.lang.Class hubitat.zwave.Zwave.getSwitchColorV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSwitchColorV2()
  public static java.lang.Class hubitat.zwave.Zwave.getSwitchColorV3()
  public static java.lang.Class hubitat.zwave.Zwave.getSwitchMultilevelV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSwitchMultilevelV2()
  public static java.lang.Class hubitat.zwave.Zwave.getSwitchMultilevelV3()
  public static java.lang.Class hubitat.zwave.Zwave.getSwitchToggleBinaryV1()
  public static java.lang.Class hubitat.zwave.Zwave.getSwitchToggleMultilevelV1()
  public static java.lang.Class hubitat.zwave.Zwave.getTariffConfigV1()
  public static java.lang.Class hubitat.zwave.Zwave.getTariffTblMonitorV1()
  public static java.lang.Class hubitat.zwave.Zwave.getThermostatFanModeV1()
  public static java.lang.Class hubitat.zwave.Zwave.getThermostatFanModeV2()
  public static java.lang.Class hubitat.zwave.Zwave.getThermostatFanModeV3()
  public static java.lang.Class hubitat.zwave.Zwave.getThermostatFanStateV1()
  public static java.lang.Class hubitat.zwave.Zwave.getThermostatHeatingV1()
  public static java.lang.Class hubitat.zwave.Zwave.getThermostatModeV1()
  public static java.lang.Class hubitat.zwave.Zwave.getThermostatModeV2()
  public static java.lang.Class hubitat.zwave.Zwave.getThermostatOperatingStateV1()
  public static java.lang.Class hubitat.zwave.Zwave.getThermostatOperatingStateV2()
  public static java.lang.Class hubitat.zwave.Zwave.getThermostatSetbackV1()
  public static java.lang.Class hubitat.zwave.Zwave.getThermostatSetpointV1()
  public static java.lang.Class hubitat.zwave.Zwave.getThermostatSetpointV2()
  public static java.lang.Class hubitat.zwave.Zwave.getTimeParametersV1()
  public static java.lang.Class hubitat.zwave.Zwave.getTimeV1()
  public static java.lang.Class hubitat.zwave.Zwave.getTimeV2()
  public static java.lang.Class hubitat.zwave.Zwave.getTransportServiceV1()
  public static java.lang.Class hubitat.zwave.Zwave.getUserCodeV1()
  public static java.lang.Class hubitat.zwave.Zwave.getVersionV1()
  public static java.lang.Class hubitat.zwave.Zwave.getVersionV2()
  public static java.lang.Class hubitat.zwave.Zwave.getVersionV3()
  public static java.lang.Class hubitat.zwave.Zwave.getWakeUpV1()
  public static java.lang.Class hubitat.zwave.Zwave.getWakeUpV2()
  public static hubitat.zwave.Command hubitat.zwave.Zwave.parse(java.lang.String)
  public static hubitat.zwave.Command hubitat.zwave.Zwave.parse(java.lang.String,boolean)
  public static hubitat.zwave.Command hubitat.zwave.Zwave.parse(java.lang.String,java.util.Map)
  public static hubitat.zwave.Command hubitat.zwave.Zwave.parse(java.lang.String,java.util.Map,boolean)
]

and a dump of hubitat.zwave.commandclasses.VersionV1

class hubitat.zwave.commandclasses.VersionV1:
Methods:[
  public static hubitat.zwave.commands.versionv1.VersionCommandClassGet hubitat.zwave.commandclasses.VersionV1.versionCommandClassGet()
  public static hubitat.zwave.commands.versionv1.VersionCommandClassGet hubitat.zwave.commandclasses.VersionV1.versionCommandClassGet(java.util.Map)
  public static hubitat.zwave.commands.versionv1.VersionCommandClassReport hubitat.zwave.commandclasses.VersionV1.versionCommandClassReport()
  public static hubitat.zwave.commands.versionv1.VersionCommandClassReport hubitat.zwave.commandclasses.VersionV1.versionCommandClassReport(java.util.Map)
  public static hubitat.zwave.commands.versionv1.VersionGet hubitat.zwave.commandclasses.VersionV1.versionGet()
  public static hubitat.zwave.commands.versionv1.VersionGet hubitat.zwave.commandclasses.VersionV1.versionGet(java.util.Map)
  public static hubitat.zwave.commands.versionv1.VersionReport hubitat.zwave.commandclasses.VersionV1.versionReport()
  public static hubitat.zwave.commands.versionv1.VersionReport hubitat.zwave.commandclasses.VersionV1.versionReport(java.util.Map)
]
biocomp commented 4 years ago

Are you saying that the issue is that zwave class is not up to date? That's very likely the case. Is this what you're fixing in your #37 ? Or are you asking how to mock the code in question? Or both? :)

cmorse commented 4 years ago

This is separate from what's being fixed in #37. I'm asking how to mock the code in question. I tried to do it myself, but I am not good enough with Groovy.

biocomp commented 4 years ago

Okay, so I think test should be looking something like this:

    def "Basic validation"() {
        setup:
            VersionGet versionGet = Mock{
                _*format() >> "Not sure what's supposed to be returned"
            }

            VersionV1 versionv1 = Mock{
                _*versionGet() >> versionGet
            }

            Zwave zwave = Mock{
                _*getVersionV1() >> versionv1
            }

            DeviceExecutor api = Mock{
                _*getZwave() >> zwave
            }

        expect:
            def script = sandbox.run(api: api)
            script.updated()
    }

The problem with this is that getZwave() of DeviceExecutor returns me.biocomp.hubitat_ci.api.device_api.zwave.Zwave trait which is currently empty.

You'll need to update it to match the dump that you've made, add VersionV1 and VersionGet mocks too, and then the example should work.

Sorry about that, as you have noticed, my device implementation is far from being complete. Thanks for helping me out with it. I might find time adding it later, but not sure when I have enough free time.

cmorse commented 4 years ago

Thanks for the help.

I'm still having some issues getting this working.

I implemented Zwave and VersionV1 as follows:

package me.biocomp.hubitat_ci.api.device_api.zwave

trait Zwave {
    abstract VersionV1 getVersionV1()
}
package me.biocomp.hubitat_ci.api.device_api.zwave.commandclasses

import me.biocomp.hubitat_ci.api.device_api.zwave.commands.versionv1.VersionGet

trait VersionV1 {
    abstract VersionGet versionGet()
}

But, this doesn't work. I get a Cannot invoke method format() on null object. A breakpoint placed in the Mock override of versionGet is never called.

But, if I rename the versionGet function to versionGet2, it starts working. My only idea for why is that Groovy thinks that versionGet is a getter for a property called version? If that is the case, I am not sure how to fix it.

biocomp commented 4 years ago

Seems like some kind of an issue with Spock framework and versionGet variable name matching method name. I renamed it to versionGetMock, and now it seems to be working for me (after updating Zwave, Version1 and VersionGet classes with your changes).

    def "Smoke test for a few zwave properties"()
    {
        setup:
            VersionGet versionGetMock = Mock{
                _*format() >> "Not sure what's supposed to be returned"
            }

            VersionV1 versionv1 = Mock{
                _*versionGet() >> versionGetMock
            }

            Zwave zwave = Mock{
                _*getVersionV1() >> versionv1
            }

            DeviceExecutor api = Mock{
                _*getZwave() >> zwave
            }

            def script = new HubitatDeviceSandbox("""
def installed() {}

def updated() {
    def cmds = []

    assert zwave != null
    assert zwave.versionV1 != null
    assert zwave.versionV1.versionGet() != null
    assert zwave.versionV1.versionGet().format() == "Not sure what's supposed to be returned"

    true
}
""").run(validationFlags: [Flags.DontValidateMetadata, Flags.DontRequireParseMethodInDevice], api: api)

        expect:
            script.updated()
    }
cmorse commented 4 years ago

That worked! Thanks! Are there any other options for doing this? Doing it this way gets rather tedious when you have code that uses a lot of zwave commands.

biocomp commented 4 years ago

I think the answer is always "it depends" :) For example, it'll depend on what you want these methods and properties of zwave objects to return. And how you defined zwave classes you're mocking (which we should definitely add to hubitat_ci).

So maybe if you show me a code sample that you'd want tested (with all the complexity you're talking about), I can try to advise something specific.

I'm not sure there's a way for me to mock all of these zwave classes in hubitat_ci in a way that is useful for users, and not too complicated to implement. Or maybe instead of traits, we can define zwave classes to be real classes, and return some default values from their methods, this way you would not need to override too many methods/properties.

But in general, it's probably going to be something boring along these lines:

  1. Move mock creation code to a separate method/fixture and reuse it
  2. Using some other mock creation techniques like constructing objects from maps (like here), which may be less code.
  3. Potentially separate zwave-specific logic from domain logic, and not test zwave-specific logic