gammazero / nexus

Full-feature WAMP v2 router and client written in Go
MIT License
268 stars 58 forks source link

Provide test helpers or examples of how to test an InvocationHandler #165

Closed bkneis closed 1 year ago

bkneis commented 5 years ago

I did not see any template for raising issues in the repo so if this does not meet an expected standard please let me know and I will update. I did check the previous issues though and could not see any related.

Currently I am using nexus and have registered several procedures, but I am at a bit lost on how I can test these procedure's InvocationHandlers. Could nexus either provide test helpers, to make it easier to do things like mocking of dependencies or at least provide a solid example of a unit tested procedure in the documentation?

martin31821 commented 5 years ago

I created a WAMP test and specification framework a while ago, but it's not ready for the public yet. Basically the idea is to spin up a simple test router and make a number of calls to specified URIs while expecting several side effects.

gammazero commented 5 years ago

@bkneis To test invocation procedures, my tests typically start a router and a caller that makes RPC calls and checks for the expected responses from this invocation handlers. So, the natural evolution of this is to provide a test framework to automate this, as you are looking for. I think this is an excellent feature request, and I will look into providing this or directing users to facilities that @martin31821 may provide if available.

martin31821 commented 5 years ago

So, I can give you a bit of a preview here.

We created a simple DSL which can be used to specify the inputs, outputs and behaviors for one or more WAMP endpoints. After some analysis, we eventually found that implementing and testing a single endpoint is not feasible, since it could be that an endpoint may generate publishes to RPC topics or interacts with Meta-APIs the router provides, so we extended the specification language to be able to correctly handle such cases. It now looks like the following:

# Import some standard error messages
import "stdlib"

# Define a namespace
namespace foo.bar {
  # Define some custom errors (args and kwargs might be specified here as well)
  error test {}
  error internal-error {}

  # A nested namespace
  namespace blah {
    # Define a complex type within the nested namespace
    type result {
      vendor string,
      name string,
      description string,
      imageURI string,
    }

    # Define a RPC with the signature, exceptions and return values
    func resolve {
      signature {
        id string,
      }
      returns {
        description foo.bar.blah.result,
      }
      throws {
        foo.bar.internal-error,
        foo.bar.test,
      }
    }

    # Define a behavior (== test case)
    # a behavior is a list of steps or macros which are executed in the specified order.
    behavior invalid-call {
      control { hook { CODE_TO_RUN_BEFORE_TEST } }
      control {
        start {
          env {
            name "ENV_VARIABLE"
            value "http://localhost:7358"
          }
        }
      }
      call foo.bar.blah.resolve {
        params {
          id "invalid"
        }
        throws foo.bar.test { }
      }
      control { hook { CODE_TO_RUN_AFTER_TEST } }
    }
  }
}

# Bind the definitions above together into a 'service' (== your microservice application)
service test {
  # Specify all RPCs your service registers
  registers {
    foo.bar.blah.resolve,
  }
  # Specify the behaviors to test against your implementation.
  implements {
    foo.bar.blah.invalid-call,
  }
}

/cc @fin-ger @johannwagner

KSDaemon commented 1 year ago

Right now there are examples with simple RPCs, progressive calls and progressive call results. Also that all is covered with basic tests, so you can look there too. Closing for now. Feel free to make updates to docs and/or examples in case you feel it can be improved. This will help other developers in the future!