Repository: knative/client
Pull request #901 (2020-06-22 14:58:39)

Fix Panic for kn source apiserver and kn source binding describe with Sink URI
## Description

Following up on #848 and #900, this pull request updates `kn source apiserver` and `kn source binding describe` to avoid a panic when using a Sink that has a URI instead of a Ref. 

This pull request also updates unit tests for `apiserver` and `binding` as some tests are not asserting whether output of commands is correct. 

/lint

knative-prow-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative/client/pull/901#" title="Author self-approved">danielhelfand</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **sixolet**
You can assign the PR to them by writing `/assign @sixolet` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=knative%2Fclient).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/knative/client/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["sixolet"]} -->
knative-prow-robot: Hi @danielhelfand. Thanks for your PR.

I'm waiting for a [knative](https://github.com/orgs/knative/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://github.com/orgs/knative/people) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=knative%2Fclient).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

maximilien: /ok-to-test
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/source/apiserver/describe.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/901/pull-knative-client-go-coverage/1275180781563547653/artifacts/line-cov.html#file3) | 85.2% | 86.9% | 1.6
[pkg/kn/commands/source/binding/describe.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/901/pull-knative-client-go-coverage/1275180781563547653/artifacts/line-cov.html#file8) | 90.8% | 92.3% | 1.5

knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/source/apiserver/describe.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/901/pull-knative-client-go-coverage/1275214755287535618/artifacts/line-cov.html#file3) | 85.2% | 86.9% | 1.6
[pkg/kn/commands/source/binding/describe.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/901/pull-knative-client-go-coverage/1275214755287535618/artifacts/line-cov.html#file8) | 90.8% | 92.3% | 1.5

knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/source/apiserver/describe.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/901/pull-knative-client-go-coverage/1275235895389196288/artifacts/line-cov.html#file3) | 85.2% | 86.9% | 1.6
[pkg/kn/commands/source/binding/describe.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/901/pull-knative-client-go-coverage/1275235895389196288/artifacts/line-cov.html#file8) | 90.8% | 92.3% | 1.5

knative-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative/client/pull/901#" title="Author self-approved">danielhelfand</a>*, *<a href="https://github.com/knative/client/pull/901#pullrequestreview-435437792" title="Approved">navidshaikh</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=knative%2Fclient).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/knative/client/blob/master/OWNERS)~~ [navidshaikh]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index 649c2c16..d3b9d70b 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -18,6 +18,10 @@
 |===
 | | Description | PR
 
+| 🐛
+| Fix panic for `kn source apiserver` and `kn source binding` describe with Sink URI
+| https://github.com/knative/client/pull/901[#901]
+
 | 🐛
 | Fix Panic for `kn trigger describe` with Sink URI
 | https://github.com/knative/client/pull/900[#900]
@@ -25,6 +29,10 @@
 | 🎁
 | Group commands in usage output with `kn --help`
 | https://github.com/knative/client/pull/887[#887]
+
+| 🎁
+| Add commands to manage Broker resource
+| https://github.com/knative/client/pull/894[#894]
 |===
 
 ## v0.15.2 (2020-06-16)
diff --git a/docs/cmd/kn.md b/docs/cmd/kn.md
index 60ec6e8c..27698194 100644
--- a/docs/cmd/kn.md
+++ b/docs/cmd/kn.md
@@ -19,6 +19,7 @@ kn is the command line interface for managing Knative Serving and Eventing resou
 
 ### SEE ALSO
 
+* [kn broker](kn_broker.md)	 - Manage message broker
 * [kn completion](kn_completion.md)	 - Output shell completion code
 * [kn options](kn_options.md)	 - Print the list of flags inherited by all commands
 * [kn plugin](kn_plugin.md)	 - Manage kn plugins
diff --git a/docs/cmd/kn_broker.md b/docs/cmd/kn_broker.md
new file mode 100644
index 00000000..104788db
--- /dev/null
+++ b/docs/cmd/kn_broker.md
@@ -0,0 +1,34 @@
+## kn broker
+
+Manage message broker
+
+### Synopsis
+
+Manage message broker
+
+```
+kn broker
+```
+
+### Options
+
+```
+  -h, --help   help for broker
+```
+
+### Options inherited from parent commands
+
+```
+      --config string       kn configuration file (default: ~/.config/kn/config.yaml)
+      --kubeconfig string   kubectl configuration file (default: ~/.kube/config)
+      --log-http            log http traffic
+```
+
+### SEE ALSO
+
+* [kn](kn.md)	 - kn manages Knative Serving and Eventing resources
+* [kn broker create](kn_broker_create.md)	 - Create a broker
+* [kn broker delete](kn_broker_delete.md)	 - Delete a broker
+* [kn broker describe](kn_broker_describe.md)	 - Describe broker
+* [kn broker list](kn_broker_list.md)	 - List brokers
+
diff --git a/docs/cmd/kn_broker_create.md b/docs/cmd/kn_broker_create.md
new file mode 100644
index 00000000..fdee69d1
--- /dev/null
+++ b/docs/cmd/kn_broker_create.md
@@ -0,0 +1,42 @@
+## kn broker create
+
+Create a broker
+
+### Synopsis
+
+Create a broker
+
+```
+kn broker create NAME
+```
+
+### Examples
+
+```
+
+  # Create a broker 'mybroker' in the current namespace
+  kn broker create mybroker
+
+  # Create a broker 'mybroker' in the 'myproject' namespace
+  kn broker create mybroker --namespace myproject
+```
+
+### Options
+
+```
+  -h, --help               help for create
+  -n, --namespace string   Specify the namespace to operate in.
+```
+
+### Options inherited from parent commands
+
+```
+      --config string       kn configuration file (default: ~/.config/kn/config.yaml)
+      --kubeconfig string   kubectl configuration file (default: ~/.kube/config)
+      --log-http            log http traffic
+```
+
+### SEE ALSO
+
+* [kn broker](kn_broker.md)	 - Manage message broker
+
diff --git a/docs/cmd/kn_broker_delete.md b/docs/cmd/kn_broker_delete.md
new file mode 100644
index 00000000..67b1c7f6
--- /dev/null
+++ b/docs/cmd/kn_broker_delete.md
@@ -0,0 +1,46 @@
+## kn broker delete
+
+Delete a broker
+
+### Synopsis
+
+Delete a broker
+
+```
+kn broker delete NAME
+```
+
+### Examples
+
+```
+
+  # Delete a broker 'mybroker' in the current namespace
+  kn broker create mybroker
+
+  # Delete a broker 'mybroker' in the 'myproject' namespace
+  kn broker create mybroker --namespace myproject
+```
+
+### Options
+
+```
+      --async              DEPRECATED: please use --no-wait instead. Do not wait for 'broker delete' operation to be completed. (default true)
+  -h, --help               help for delete
+  -n, --namespace string   Specify the namespace to operate in.
+      --no-wait            Do not wait for 'broker delete' operation to be completed. (default true)
+      --wait               Wait for 'broker delete' operation to be completed.
+      --wait-timeout int   Seconds to wait before giving up on waiting for broker to be deleted. (default 600)
+```
+
+### Options inherited from parent commands
+
+```
+      --config string       kn configuration file (default: ~/.config/kn/config.yaml)
+      --kubeconfig string   kubectl configuration file (default: ~/.kube/config)
+      --log-http            log http traffic
+```
+
+### SEE ALSO
+
+* [kn broker](kn_broker.md)	 - Manage message broker
+
diff --git a/docs/cmd/kn_broker_describe.md b/docs/cmd/kn_broker_describe.md
new file mode 100644
index 00000000..808bcf00
--- /dev/null
+++ b/docs/cmd/kn_broker_describe.md
@@ -0,0 +1,42 @@
+## kn broker describe
+
+Describe broker
+
+### Synopsis
+
+Describe broker
+
+```
+kn broker describe NAME
+```
+
+### Examples
+
+```
+
+  # Describe broker 'mybroker' in the current namespace
+  kn broker describe mybroker
+
+  # Describe broker 'mybroker' in the 'myproject' namespace
+  kn broker describe mybroker --namespace myproject
+```
+
+### Options
+
+```
+  -h, --help               help for describe
+  -n, --namespace string   Specify the namespace to operate in.
+```
+
+### Options inherited from parent commands
+
+```
+      --config string       kn configuration file (default: ~/.config/kn/config.yaml)
+      --kubeconfig string   kubectl configuration file (default: ~/.kube/config)
+      --log-http            log http traffic
+```
+
+### SEE ALSO
+
+* [kn broker](kn_broker.md)	 - Manage message broker
+
diff --git a/docs/cmd/kn_broker_list.md b/docs/cmd/kn_broker_list.md
new file mode 100644
index 00000000..506de7ee
--- /dev/null
+++ b/docs/cmd/kn_broker_list.md
@@ -0,0 +1,47 @@
+## kn broker list
+
+List brokers
+
+### Synopsis
+
+List brokers
+
+```
+kn broker list
+```
+
+### Examples
+
+```
+
+  # List all brokers
+  kn broker list
+
+  # List all brokers in JSON output format
+  kn broker list -o json
+```
+
+### Options
+
+```
+  -A, --all-namespaces                If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.
+      --allow-missing-template-keys   If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats. (default true)
+  -h, --help                          help for list
+  -n, --namespace string              Specify the namespace to operate in.
+      --no-headers                    When using the default output format, don't print headers (default: print headers).
+  -o, --output string                 Output format. One of: json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-file.
+      --template string               Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].
+```
+
+### Options inherited from parent commands
+
+```
+      --config string       kn configuration file (default: ~/.config/kn/config.yaml)
+      --kubeconfig string   kubectl configuration file (default: ~/.kube/config)
+      --log-http            log http traffic
+```
+
+### SEE ALSO
+
+* [kn broker](kn_broker.md)	 - Manage message broker
+
diff --git a/docs/cmd/kn_source_ping_create.md b/docs/cmd/kn_source_ping_create.md
index 8c26e709..f2271191 100644
--- a/docs/cmd/kn_source_ping_create.md
+++ b/docs/cmd/kn_source_ping_create.md
@@ -7,7 +7,7 @@ Create a ping source
 Create a ping source
 
 ```
-kn source ping create NAME
+kn source ping create NAME --sink SINK
 ```
 
 ### Examples
diff --git a/pkg/eventing/v1beta1/client.go b/pkg/eventing/v1beta1/client.go
index 9fb2ee3f..df1b4404 100644
--- a/pkg/eventing/v1beta1/client.go
+++ b/pkg/eventing/v1beta1/client.go
@@ -15,9 +15,12 @@
 package v1beta1
 
 import (
+	"time"
+
 	apis_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/watch"
 	v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1"
 	"knative.dev/eventing/pkg/client/clientset/versioned/scheme"
 	client_v1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1"
@@ -25,6 +28,7 @@ import (
 
 	kn_errors "knative.dev/client/pkg/errors"
 	"knative.dev/client/pkg/util"
+	"knative.dev/client/pkg/wait"
 )
 
 // KnEventingClient to Eventing Sources. All methods are relative to the
@@ -42,6 +46,14 @@ type KnEventingClient interface {
 	ListTriggers() (*v1beta1.TriggerList, error)
 	// UpdateTrigger is used to update an instance of trigger
 	UpdateTrigger(trigger *v1beta1.Trigger) error
+	// CreateBroker is used to create an instance of broker
+	CreateBroker(broker *v1beta1.Broker) error
+	// GetBroker is used to get an instance of broker
+	GetBroker(name string) (*v1beta1.Broker, error)
+	// DeleteBroker is used to delete an instance of broker
+	DeleteBroker(name string, timeout time.Duration) error
+	// ListBroker returns list of broker CRDs
+	ListBrokers() (*v1beta1.BrokerList, error)
 }
 
 // KnEventingClient is a combination of Sources client interface and namespace
@@ -93,7 +105,7 @@ func (c *knEventingClient) ListTriggers() (*v1beta1.TriggerList, error) {
 		return nil, kn_errors.GetError(err)
 	}
 	triggerListNew := triggerList.DeepCopy()
-	err = updateTriggerGvk(triggerListNew)
+	err = updateEventingGVK(triggerListNew)
 	if err != nil {
 		return nil, err
 	}
@@ -101,7 +113,7 @@ func (c *knEventingClient) ListTriggers() (*v1beta1.TriggerList, error) {
 	triggerListNew.Items = make([]v1beta1.Trigger, len(triggerList.Items))
 	for idx, trigger := range triggerList.Items {
 		triggerClone := trigger.DeepCopy()
-		err := updateTriggerGvk(triggerClone)
+		err := updateEventingGVK(triggerClone)
 		if err != nil {
 			return nil, err
 		}
@@ -125,7 +137,7 @@ func (c *knEventingClient) Namespace() string {
 }
 
 // update with the v1beta1 group + version
-func updateTriggerGvk(obj runtime.Object) error {
+func updateEventingGVK(obj runtime.Object) error {
 	return util.UpdateGroupVersionKindWithScheme(obj, v1beta1.SchemeGroupVersion, scheme.Scheme)
 }
 
@@ -200,3 +212,104 @@ func (b *TriggerBuilder) Filters(filters map[string]string) *TriggerBuilder {
 func (b *TriggerBuilder) Build() *v1beta1.Trigger {
 	return b.trigger
 }
+
+// CreateBroker is used to create an instance of broker
+func (c *knEventingClient) CreateBroker(broker *v1beta1.Broker) error {
+	_, err := c.client.Brokers(c.namespace).Create(broker)
+	if err != nil {
+		return kn_errors.GetError(err)
+	}
+	return nil
+}
+
+// GetBroker is used to get an instance of broker
+func (c *knEventingClient) GetBroker(name string) (*v1beta1.Broker, error) {
+	trigger, err := c.client.Brokers(c.namespace).Get(name, apis_v1.GetOptions{})
+	if err != nil {
+		return nil, kn_errors.GetError(err)
+	}
+	return trigger, nil
+}
+
+// WatchBroker is used to create watcher object
+func (c *knEventingClient) WatchBroker(name string, timeout time.Duration) (watch.Interface, error) {
+	return wait.NewWatcher(c.client.Brokers(c.namespace).Watch,
+		c.client.RESTClient(), c.namespace, "brokers", name, timeout)
+}
+
+// DeleteBroker is used to delete an instance of broker and wait for completion until given timeout
+// For `timeout == 0` delete is performed async without any wait
+func (c *knEventingClient) DeleteBroker(name string, timeout time.Duration) error {
+	if timeout == 0 {
+		return c.deleteBroker(name, apis_v1.DeletePropagationBackground)
+	}
+	waitC := make(chan error)
+	go func() {
+		waitForEvent := wait.NewWaitForEvent("broker", c.WatchBroker, func(evt *watch.Event) bool { return evt.Type == watch.Deleted })
+		err, _ := waitForEvent.Wait(name, wait.Options{Timeout: &timeout}, wait.NoopMessageCallback())
+		waitC <- err
+	}()
+	err := c.deleteBroker(name, apis_v1.DeletePropagationForeground)
+	if err != nil {
+		return err
+	}
+	return <-waitC
+}
+
+// deleteBroker is used to delete an instance of broker
+func (c *knEventingClient) deleteBroker(name string, propagationPolicy apis_v1.DeletionPropagation) error {
+	err := c.client.Brokers(c.namespace).Delete(name, &apis_v1.DeleteOptions{PropagationPolicy: &propagationPolicy})
+	if err != nil {
+		return kn_errors.GetError(err)
+	}
+	return nil
+}
+
+// ListBrokers is used to retrieve the list of broker instances
+func (c *knEventingClient) ListBrokers() (*v1beta1.BrokerList, error) {
+	brokerList, err := c.client.Brokers(c.namespace).List(apis_v1.ListOptions{})
+	if err != nil {
+		return nil, kn_errors.GetError(err)
+	}
+	brokerListNew := brokerList.DeepCopy()
+	err = updateEventingGVK(brokerListNew)
+	if err != nil {
+		return nil, err
+	}
+
+	brokerListNew.Items = make([]v1beta1.Broker, len(brokerList.Items))
+	for idx, trigger := range brokerList.Items {
+		triggerClone := trigger.DeepCopy()
+		err := updateEventingGVK(triggerClone)
+		if err != nil {
+			return nil, err
+		}
+		brokerListNew.Items[idx] = *triggerClone
+	}
+	return brokerListNew, nil
+}
+
+// BrokerBuilder is for building the broker
+type BrokerBuilder struct {
+	broker *v1beta1.Broker
+}
+
+// NewBrokerBuilder for building broker object
+func NewBrokerBuilder(name string) *BrokerBuilder {
+	return &BrokerBuilder{broker: &v1beta1.Broker{
+		ObjectMeta: meta_v1.ObjectMeta{
+			Name: name,
+		},
+	}}
+}
+
+// Namespace for broker builder
+func (b *BrokerBuilder) Namespace(ns string) *BrokerBuilder {
+	b.broker.Namespace = ns
+	return b
+}
+
+// Build to return an instance of broker object
+func (b *BrokerBuilder) Build() *v1beta1.Broker {
+	return b.broker
+}
diff --git a/pkg/eventing/v1beta1/client_mock.go b/pkg/eventing/v1beta1/client_mock.go
index f93c800f..c0cabd78 100644
--- a/pkg/eventing/v1beta1/client_mock.go
+++ b/pkg/eventing/v1beta1/client_mock.go
@@ -16,6 +16,7 @@ package v1beta1
 
 import (
 	"testing"
+	"time"
 
 	v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1"
 
@@ -114,6 +115,50 @@ func (c *MockKnEventingClient) UpdateTrigger(trigger *v1beta1.Trigger) error {
 	return mock.ErrorOrNil(call.Result[0])
 }
 
+// CreateBroker records a call for CreateBroker with the expected error
+func (sr *EventingRecorder) CreateBroker(broker interface{}, err error) {
+	sr.r.Add("CreateBroker", []interface{}{broker}, []interface{}{err})
+}
+
+// CreateBroker performs a previously recorded action
+func (c *MockKnEventingClient) CreateBroker(broker *v1beta1.Broker) error {
+	call := c.recorder.r.VerifyCall("CreateBroker", broker)
+	return mock.ErrorOrNil(call.Result[0])
+}
+
+// GetBroker records a call for GetBroker with the expected object or error. Either trigger or err should be nil
+func (sr *EventingRecorder) GetBroker(name interface{}, broker *v1beta1.Broker, err error) {
+	sr.r.Add("GetBroker", []interface{}{name}, []interface{}{broker, err})
+}
+
+// GetBroker performs a previously recorded action
+func (c *MockKnEventingClient) GetBroker(name string) (*v1beta1.Broker, error) {
+	call := c.recorder.r.VerifyCall("GetBroker", name)
+	return call.Result[0].(*v1beta1.Broker), mock.ErrorOrNil(call.Result[1])
+}
+
+// DeleteBroker records a call for DeleteBroker with the expected error (nil if none)
+func (sr *EventingRecorder) DeleteBroker(name, timeout interface{}, err error) {
+	sr.r.Add("DeleteBroker", []interface{}{name, timeout}, []interface{}{err})
+}
+
+// DeleteBroker performs a previously recorded action, failing if non has been registered
+func (c *MockKnEventingClient) DeleteBroker(name string, timeout time.Duration) error {
+	call := c.recorder.r.VerifyCall("DeleteBroker", name, timeout)
+	return mock.ErrorOrNil(call.Result[0])
+}
+
+// ListBrokers records a call for ListBrokers with the expected result and error (nil if none)
+func (sr *EventingRecorder) ListBrokers(brokerList *v1beta1.BrokerList, err error) {
+	sr.r.Add("ListBrokers", nil, []interface{}{brokerList, err})
+}
+
+// ListBrokers performs a previously recorded action
+func (c *MockKnEventingClient) ListBrokers() (*v1beta1.BrokerList, error) {
+	call := c.recorder.r.VerifyCall("ListBrokers")
+	return call.Result[0].(*v1beta1.BrokerList), mock.ErrorOrNil(call.Result[1])
+}
+
 // Validate validates whether every recorded action has been called
 func (sr *EventingRecorder) Validate() {
 	sr.r.CheckThatAllRecordedMethodsHaveBeenCalled()
diff --git a/pkg/kn/commands/broker/broker.go b/pkg/kn/commands/broker/broker.go
new file mode 100644
index 00000000..bbb93349
--- /dev/null
+++ b/pkg/kn/commands/broker/broker.go
@@ -0,0 +1,36 @@
+/*
+Copyright 2020 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package broker
+
+import (
+	"github.com/spf13/cobra"
+
+	"knative.dev/client/pkg/kn/commands"
+)
+
+// NewBrokerCommand represents broker management commands
+func NewBrokerCommand(p *commands.KnParams) *cobra.Command {
+	brokerCmd := &cobra.Command{
+		Use:   "broker",
+		Short: "Manage message broker",
+	}
+	brokerCmd.AddCommand(NewBrokerCreateCommand(p))
+	brokerCmd.AddCommand(NewBrokerDescribeCommand(p))
+	brokerCmd.AddCommand(NewBrokerDeleteCommand(p))
+	brokerCmd.AddCommand(NewBrokerListCommand(p))
+	return brokerCmd
+}
diff --git a/pkg/kn/commands/broker/create.go b/pkg/kn/commands/broker/create.go
new file mode 100644
index 00000000..81940e6d
--- /dev/null
+++ b/pkg/kn/commands/broker/create.go
@@ -0,0 +1,75 @@
+/*
+Copyright 2020 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package broker
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/spf13/cobra"
+
+	clientv1beta1 "knative.dev/client/pkg/eventing/v1beta1"
+	"knative.dev/client/pkg/kn/commands"
+)
+
+var createExample = `
+  # Create a broker 'mybroker' in the current namespace
+  kn broker create mybroker
+
+  # Create a broker 'mybroker' in the 'myproject' namespace
+  kn broker create mybroker --namespace myproject`
+
+// NewBrokerCreateCommand represents command to create new broker instance
+func NewBrokerCreateCommand(p *commands.KnParams) *cobra.Command {
+
+	cmd := &cobra.Command{
+		Use:     "create NAME",
+		Short:   "Create a broker",
+		Example: createExample,
+		RunE: func(cmd *cobra.Command, args []string) (err error) {
+			if len(args) != 1 {
+				return errors.New("'broker create' requires the broker name given as single argument")
+			}
+			name := args[0]
+
+			namespace, err := p.GetNamespace(cmd)
+			if err != nil {
+				return err
+			}
+
+			eventingClient, err := p.NewEventingClient(namespace)
+			if err != nil {
+				return err
+			}
+
+			brokerBuilder := clientv1beta1.
+				NewBrokerBuilder(name).
+				Namespace(namespace)
+
+			err = eventingClient.CreateBroker(brokerBuilder.Build())
+			if err != nil {
+				return fmt.Errorf(
+					"cannot create broker '%s' in namespace '%s' "+
+						"because: %s", name, namespace, err)
+			}
+			fmt.Fprintf(cmd.OutOrStdout(), "Broker '%s' successfully created in namespace '%s'.\n", args[0], namespace)
+			return nil
+		},
+	}
+	commands.AddNamespaceFlags(cmd.Flags(), false)
+	return cmd
+}
diff --git a/pkg/kn/commands/broker/delete.go b/pkg/kn/commands/broker/delete.go
new file mode 100644
index 00000000..1af0f8a1
--- /dev/null
+++ b/pkg/kn/commands/broker/delete.go
@@ -0,0 +1,77 @@
+/*
+Copyright 2020 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package broker
+
+import (
+	"errors"
+	"fmt"
+	"time"
+
+	"github.com/spf13/cobra"
+
+	"knative.dev/client/pkg/kn/commands"
+)
+
+var deleteExample = `
+  # Delete a broker 'mybroker' in the current namespace
+  kn broker create mybroker
+
+  # Delete a broker 'mybroker' in the 'myproject' namespace
+  kn broker create mybroker --namespace myproject`
+
+// NewBrokerDeleteCommand represents command to existing delete broker
+func NewBrokerDeleteCommand(p *commands.KnParams) *cobra.Command {
+	var waitFlags commands.WaitFlags
+
+	cmd := &cobra.Command{
+		Use:     "delete NAME",
+		Short:   "Delete a broker",
+		Example: deleteExample,
+		RunE: func(cmd *cobra.Command, args []string) (err error) {
+			if len(args) != 1 {
+				return errors.New("'broker delete' requires the broker name given as single argument")
+			}
+			name := args[0]
+
+			namespace, err := p.GetNamespace(cmd)
+			if err != nil {
+				return err
+			}
+
+			eventingClient, err := p.NewEventingClient(namespace)
+			if err != nil {
+				return err
+			}
+
+			timeout := time.Duration(0)
+			if waitFlags.Wait {
+				timeout = time.Duration(waitFlags.TimeoutInSeconds) * time.Second
+			}
+			err = eventingClient.DeleteBroker(name, timeout)
+			if err != nil {
+				return fmt.Errorf(
+					"cannot delete broker '%s' in namespace '%s' "+
+						"because: %s", name, namespace, err)
+			}
+			fmt.Fprintf(cmd.OutOrStdout(), "Broker '%s' successfully deleted in namespace '%s'.\n", args[0], namespace)
+			return nil
+		},
+	}
+	commands.AddNamespaceFlags(cmd.Flags(), false)
+	waitFlags.AddConditionWaitFlags(cmd, commands.WaitDefaultTimeout, "delete", "broker", "deleted")
+	return cmd
+}
diff --git a/pkg/kn/commands/broker/describe.go b/pkg/kn/commands/broker/describe.go
new file mode 100644
index 00000000..f18c9d46
--- /dev/null
+++ b/pkg/kn/commands/broker/describe.go
@@ -0,0 +1,84 @@
+/*
+Copyright 2020 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package broker
+
+import (
+	"errors"
+	"io"
+
+	"github.com/spf13/cobra"
+
+	v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1"
+
+	"knative.dev/client/pkg/kn/commands"
+	"knative.dev/client/pkg/printers"
+)
+
+var describeExample = `
+  # Describe broker 'mybroker' in the current namespace
+  kn broker describe mybroker
+
+  # Describe broker 'mybroker' in the 'myproject' namespace
+  kn broker describe mybroker --namespace myproject`
+
+// NewBrokerDescribeCommand represents command to describe details of broker instance
+func NewBrokerDescribeCommand(p *commands.KnParams) *cobra.Command {
+
+	cmd := &cobra.Command{
+		Use:     "describe NAME",
+		Short:   "Describe broker",
+		Example: describeExample,
+		RunE: func(cmd *cobra.Command, args []string) (err error) {
+			if len(args) != 1 {
+				return errors.New("'broker describe' requires the broker name given as single argument")
+			}
+			name := args[0]
+
+			namespace, err := p.GetNamespace(cmd)
+			if err != nil {
+				return err
+			}
+
+			eventingClient, err := p.NewEventingClient(namespace)
+			if err != nil {
+				return err
+			}
+
+			broker, err := eventingClient.GetBroker(name)
+			if err != nil {
+				return err
+			}
+			return describeBroker(cmd.OutOrStdout(), broker, false)
+		},
+	}
+	commands.AddNamespaceFlags(cmd.Flags(), false)
+	return cmd
+}
+
+// describeBroker print broker details to the provided output writer
+func describeBroker(out io.Writer, broker *v1beta1.Broker, printDetails bool) error {
+	dw := printers.NewPrefixWriter(out)
+	commands.WriteMetadata(dw, &broker.ObjectMeta, printDetails)
+	dw.WriteLine()
+	dw.WriteAttribute("Address", "").WriteAttribute("URL", broker.Status.Address.URL.String())
+	dw.WriteLine()
+	commands.WriteConditions(dw, broker.Status.Conditions, printDetails)
+	if err := dw.Flush(); err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/pkg/kn/commands/broker/list.go b/pkg/kn/commands/broker/list.go
new file mode 100644
index 00000000..a4418187
--- /dev/null
+++ b/pkg/kn/commands/broker/list.go
@@ -0,0 +1,139 @@
+/*
+Copyright 2020 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package broker
+
+import (
+	"fmt"
+
+	"github.com/spf13/cobra"
+
+	metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
+	"k8s.io/apimachinery/pkg/runtime"
+
+	"knative.dev/client/pkg/kn/commands"
+	"knative.dev/client/pkg/kn/commands/flags"
+	hprinters "knative.dev/client/pkg/printers"
+	"knative.dev/eventing/pkg/apis/eventing/v1beta1"
+)
+
+var listExample = `
+  # List all brokers
+  kn broker list
+
+  # List all brokers in JSON output format
+  kn broker list -o json`
+
+// NewBrokerListCommand represents command to list all brokers
+func NewBrokerListCommand(p *commands.KnParams) *cobra.Command {
+	brokerListFlags := flags.NewListPrintFlags(ListHandlers)
+
+	cmd := &cobra.Command{
+		Use:     "list",
+		Short:   "List brokers",
+		Example: listExample,
+		RunE: func(cmd *cobra.Command, args []string) (err error) {
+			namespace, err := p.GetNamespace(cmd)
+			if err != nil {
+				return err
+			}
+
+			eventingClient, err := p.NewEventingClient(namespace)
+			if err != nil {
+				return err
+			}
+
+			brokerList, err := eventingClient.ListBrokers()
+			if err != nil {
+				return err
+			}
+			if len(brokerList.Items) == 0 {
+				fmt.Fprintf(cmd.OutOrStdout(), "No brokers found.\n")
+				return nil
+			}
+
+			// empty namespace indicates all-namespaces flag is specified
+			if namespace == "" {
+				brokerListFlags.EnsureWithNamespace()
+			}
+
+			err = brokerListFlags.Print(brokerList, cmd.OutOrStdout())
+			if err != nil {
+				return err
+			}
+			return nil
+		},
+	}
+	commands.AddNamespaceFlags(cmd.Flags(), true)
+	brokerListFlags.AddFlags(cmd)
+	return cmd
+}
+
+// ListHandlers handles printing human readable table for `kn broker list` command's output
+func ListHandlers(h hprinters.PrintHandler) {
+	brokerColumnDefinitions := []metav1beta1.TableColumnDefinition{
+		{Name: "Namespace", Type: "string", Description: "Namespace of the Broker instance", Priority: 0},
+		{Name: "Name", Type: "string", Description: "Name of the Broker instance", Priority: 1},
+		{Name: "URL", Type: "string", Description: "URL of the Broker instance", Priority: 1},
+		{Name: "Age", Type: "string", Description: "Age of the Broker instance", Priority: 1},
+		{Name: "Conditions", Type: "string", Description: "Ready state conditions", Priority: 1},
+		{Name: "Ready", Type: "string", Description: "Ready state of the Broker instance", Priority: 1},
+		{Name: "Reason", Type: "string", Description: "Reason if state is not Ready", Priority: 1},
+	}
+	h.TableHandler(brokerColumnDefinitions, printBroker)
+	h.TableHandler(brokerColumnDefinitions, printBrokerList)
+}
+
+// printBrokerList populates the broker list table rows
+func printBrokerList(kServiceList *v1beta1.BrokerList, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) {
+	rows := make([]metav1beta1.TableRow, 0, len(kServiceList.Items))
+
+	for _, ksvc := range kServiceList.Items {
+		r, err := printBroker(&ksvc, options)
+		if err != nil {
+			return nil, err
+		}
+		rows = append(rows, r...)
+	}
+	return rows, nil
+}
+
+// printBroker populates the broker table rows
+func printBroker(broker *v1beta1.Broker, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) {
+	name := broker.Name
+	url := broker.Status.Address.URL
+	age := commands.TranslateTimestampSince(broker.CreationTimestamp)
+	conditions := commands.ConditionsValue(broker.Status.Conditions)
+	ready := commands.ReadyCondition(broker.Status.Conditions)
+	reason := commands.NonReadyConditionReason(broker.Status.Conditions)
+
+	row := metav1beta1.TableRow{
+		Object: runtime.RawExtension{Object: broker},
+	}
+
+	if options.AllNamespaces {
+		row.Cells = append(row.Cells, broker.Namespace)
+	}
+
+	row.Cells = append(row.Cells,
+		name,
+		url,
+		age,
+		conditions,
+		ready,
+		reason)
+	return []metav1beta1.TableRow{row}, nil
+}
diff --git a/pkg/kn/commands/source/apiserver/describe.go b/pkg/kn/commands/source/apiserver/describe.go
index c30ac769..58a0caec 100644
--- a/pkg/kn/commands/source/apiserver/describe.go
+++ b/pkg/kn/commands/source/apiserver/describe.go
@@ -29,7 +29,6 @@ import (
 
 // NewAPIServerDescribeCommand to describe an ApiServer source object
 func NewAPIServerDescribeCommand(p *commands.KnParams) *cobra.Command {
-
 	apiServerDescribe := &cobra.Command{
 		Use:   "describe NAME",
 		Short: "Show details of an api-server source",
@@ -110,10 +109,10 @@ func writeResources(dw printers.PrefixWriter, apiVersionKindSelectors []v1alpha2
 
 func writeSink(dw printers.PrefixWriter, sink duckv1.Destination) {
 	subWriter := dw.WriteAttribute("Sink", "")
-	subWriter.WriteAttribute("Name", sink.Ref.Name)
-	subWriter.WriteAttribute("Namespace", sink.Ref.Namespace)
 	ref := sink.Ref
 	if ref != nil {
+		subWriter.WriteAttribute("Name", sink.Ref.Name)
+		subWriter.WriteAttribute("Namespace", sink.Ref.Namespace)
 		subWriter.WriteAttribute("Kind", fmt.Sprintf("%s (%s)", sink.Ref.Kind, sink.Ref.APIVersion))
 	}
 	uri := sink.URI
diff --git a/pkg/kn/commands/source/binding/describe.go b/pkg/kn/commands/source/binding/describe.go
index c2f9bbd5..29366a02 100644
--- a/pkg/kn/commands/source/binding/describe.go
+++ b/pkg/kn/commands/source/binding/describe.go
@@ -93,12 +93,12 @@ func writeSinkBinding(dw printers.PrefixWriter, binding *v1alpha2.SinkBinding, p
 
 func writeSink(dw printers.PrefixWriter, namespace string, sink *duckv1.Destination) {
 	subWriter := dw.WriteAttribute("Sink", "")
-	if sink.Ref.Namespace != "" && sink.Ref.Namespace != namespace {
-		subWriter.WriteAttribute("Namespace", sink.Ref.Namespace)
-	}
-	subWriter.WriteAttribute("Name", sink.Ref.Name)
 	ref := sink.Ref
 	if ref != nil {
+		subWriter.WriteAttribute("Name", sink.Ref.Name)
+		if sink.Ref.Namespace != "" && sink.Ref.Namespace != namespace {
+			subWriter.WriteAttribute("Namespace", sink.Ref.Namespace)
+		}
 		subWriter.WriteAttribute("Resource", fmt.Sprintf("%s (%s)", sink.Ref.Kind, sink.Ref.APIVersion))
 	}
 	uri := sink.URI
diff --git a/pkg/kn/commands/source/ping/create.go b/pkg/kn/commands/source/ping/create.go
index b667d0d8..eca52966 100644
--- a/pkg/kn/commands/source/ping/create.go
+++ b/pkg/kn/commands/source/ping/create.go
@@ -32,7 +32,7 @@ func NewPingCreateCommand(p *commands.KnParams) *cobra.Command {
 	var sinkFlags flags.SinkFlags
 
 	cmd := &cobra.Command{
-		Use:   "create NAME",
+		Use:   "create NAME --sink SINK",
 		Short: "Create a ping source",
 		Example: `
   # Create a Ping source 'my-ping' which fires every two minutes and sends '{ value: "hello" }' to service 'mysvc' as a cloudevent
diff --git a/pkg/kn/root/root.go b/pkg/kn/root/root.go
index d738fb2b..17a77528 100644
--- a/pkg/kn/root/root.go
+++ b/pkg/kn/root/root.go
@@ -25,6 +25,7 @@ import (
 	_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
 
 	"knative.dev/client/pkg/kn/commands"
+	"knative.dev/client/pkg/kn/commands/broker"
 	"knative.dev/client/pkg/kn/commands/completion"
 	"knative.dev/client/pkg/kn/commands/options"
 	"knative.dev/client/pkg/kn/commands/plugin"
@@ -89,6 +90,7 @@ func NewRootCommand() (*cobra.Command, error) {
 			Header: "Eventing Commands:",
 			Commands: []*cobra.Command{
 				source.NewSourceCommand(p),
+				broker.NewBrokerCommand(p),
 				trigger.NewTriggerCommand(p),
 			},
 		},


Test patch:
diff --git a/pkg/eventing/v1beta1/client_mock_test.go b/pkg/eventing/v1beta1/client_mock_test.go
index 5062310c..38dc2610 100644
--- a/pkg/eventing/v1beta1/client_mock_test.go
+++ b/pkg/eventing/v1beta1/client_mock_test.go
@@ -16,6 +16,7 @@ package v1beta1
 
 import (
 	"testing"
+	"time"
 
 	v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1"
 )
@@ -33,6 +34,11 @@ func TestMockKnClient(t *testing.T) {
 	recorder.ListTriggers(nil, nil)
 	recorder.UpdateTrigger(&v1beta1.Trigger{}, nil)
 
+	recorder.CreateBroker(&v1beta1.Broker{}, nil)
+	recorder.GetBroker("foo", nil, nil)
+	recorder.DeleteBroker("foo", time.Duration(10)*time.Second, nil)
+	recorder.ListBrokers(nil, nil)
+
 	// Call all service
 	client.GetTrigger("hello")
 	client.CreateTrigger(&v1beta1.Trigger{})
@@ -40,6 +46,11 @@ func TestMockKnClient(t *testing.T) {
 	client.ListTriggers()
 	client.UpdateTrigger(&v1beta1.Trigger{})
 
+	client.CreateBroker(&v1beta1.Broker{})
+	client.GetBroker("foo")
+	client.DeleteBroker("foo", time.Duration(10)*time.Second)
+	client.ListBrokers()
+
 	// Validate
 	recorder.Validate()
 }
diff --git a/pkg/eventing/v1beta1/client_test.go b/pkg/eventing/v1beta1/client_test.go
index 57e5a86e..73832cad 100644
--- a/pkg/eventing/v1beta1/client_test.go
+++ b/pkg/eventing/v1beta1/client_test.go
@@ -17,14 +17,21 @@ package v1beta1
 import (
 	"fmt"
 	"testing"
+	"time"
 
 	"gotest.tools/assert"
+	"k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/watch"
 
+	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	client_testing "k8s.io/client-go/testing"
+	"knative.dev/client/pkg/wait"
 	v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1"
 	"knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake"
+	"knative.dev/pkg/apis"
+	duckv1 "knative.dev/pkg/apis/duck/v1"
 )
 
 var testNamespace = "test-ns"
@@ -184,6 +191,128 @@ func TestTriggerBuilder(t *testing.T) {
 
 }
 
+func TestBrokerCreate(t *testing.T) {
+	var name = "broker"
+	server, client := setup()
+
+	objNew := newBroker(name)
+
+	server.AddReactor("create", "brokers",
+		func(a client_testing.Action) (bool, runtime.Object, error) {
+			assert.Equal(t, testNamespace, a.GetNamespace())
+			name := a.(client_testing.CreateAction).GetObject().(metav1.Object).GetName()
+			if name == objNew.Name {
+				objNew.Generation = 2
+				return true, objNew, nil
+			}
+			return true, nil, fmt.Errorf("error while creating broker %s", name)
+		})
+
+	t.Run("create broker without error", func(t *testing.T) {
+		err := client.CreateBroker(objNew)
+		assert.NilError(t, err)
+	})
+
+	t.Run("create broker with an error returns an error object", func(t *testing.T) {
+		err := client.CreateBroker(newBroker("unknown"))
+		assert.ErrorContains(t, err, "unknown")
+	})
+}
+
+func TestBrokerGet(t *testing.T) {
+	var name = "foo"
+	server, client := setup()
+
+	server.AddReactor("get", "brokers",
+		func(a client_testing.Action) (bool, runtime.Object, error) {
+			name := a.(client_testing.GetAction).GetName()
+			if name == "errorBroker" {
+				return true, nil, fmt.Errorf("error while getting broker %s", name)
+			}
+			return true, newBroker(name), nil
+		})
+
+	broker, err := client.GetBroker(name)
+	assert.NilError(t, err)
+	assert.Equal(t, broker.Name, name)
+
+	_, err = client.GetBroker("errorBroker")
+	assert.ErrorContains(t, err, "errorBroker")
+}
+
+func TestBrokerDelete(t *testing.T) {
+	var name = "fooBroker"
+	server, client := setup()
+
+	server.AddReactor("delete", "brokers",
+		func(a client_testing.Action) (bool, runtime.Object, error) {
+			name := a.(client_testing.DeleteAction).GetName()
+			if name == "errorBroker" {
+				return true, nil, fmt.Errorf("error while deleting broker %s", name)
+			}
+			return true, nil, nil
+		})
+
+	err := client.DeleteBroker(name, 0)
+	assert.NilError(t, err)
+
+	err = client.DeleteBroker("errorBroker", 0)
+	assert.ErrorContains(t, err, "errorBroker", 0)
+}
+
+func TestBrokerDeleteWithWait(t *testing.T) {
+	var name = "fooBroker"
+	server, client := setup()
+
+	server.AddReactor("delete", "brokers",
+		func(a client_testing.Action) (bool, runtime.Object, error) {
+			name := a.(client_testing.DeleteAction).GetName()
+			if name == "errorBroker" {
+				return true, nil, fmt.Errorf("error while deleting broker %s", name)
+			}
+			return true, nil, nil
+		})
+
+	server.AddWatchReactor("brokers",
+		func(a client_testing.Action) (bool, watch.Interface, error) {
+			watchAction := a.(client_testing.WatchAction)
+			name, found := watchAction.GetWatchRestrictions().Fields.RequiresExactMatch("metadata.name")
+			if !found {
+				return true, nil, errors.NewNotFound(v1beta1.Resource("broker"), name)
+			}
+			w := wait.NewFakeWatch(getBrokerDeleteEvents("fooBroker"))
+			w.Start()
+			return true, w, nil
+		})
+
+	err := client.DeleteBroker(name, time.Duration(10)*time.Second)
+	assert.NilError(t, err)
+
+	err = client.DeleteBroker("errorBroker", time.Duration(10)*time.Second)
+	assert.ErrorContains(t, err, "errorBroker", time.Duration(10)*time.Second)
+}
+
+func TestBrokerList(t *testing.T) {
+	serving, client := setup()
+
+	t.Run("broker list returns a list of brokers", func(t *testing.T) {
+		broker1 := newBroker("foo1")
+		broker2 := newBroker("foo2")
+
+		serving.AddReactor("list", "brokers",
+			func(a client_testing.Action) (bool, runtime.Object, error) {
+				assert.Equal(t, testNamespace, a.GetNamespace())
+				return true, &v1beta1.BrokerList{Items: []v1beta1.Broker{*broker1, *broker2}}, nil
+			})
+
+		brokerList, err := client.ListBrokers()
+		assert.NilError(t, err)
+		assert.Assert(t, len(brokerList.Items) == 2)
+		assert.Equal(t, brokerList.Items[0].Name, "foo1")
+		assert.Equal(t, brokerList.Items[1].Name, "foo2")
+	})
+}
+
 func newTrigger(name string) *v1beta1.Trigger {
 	return NewTriggerBuilder(name).
 		Namespace(testNamespace).
@@ -191,3 +320,27 @@ func newTrigger(name string) *v1beta1.Trigger {
 		Filters(map[string]string{"type": "foo"}).
 		Build()
 }
+
+func newBroker(name string) *v1beta1.Broker {
+	return NewBrokerBuilder(name).
+		Namespace(testNamespace).
+		Build()
+}
+
+func getBrokerDeleteEvents(name string) []watch.Event {
+	return []watch.Event{
+		{watch.Added, createBrokerWithConditions(name, corev1.ConditionUnknown, corev1.ConditionUnknown, "", "msg1")},
+		{watch.Modified, createBrokerWithConditions(name, corev1.ConditionUnknown, corev1.ConditionTrue, "", "msg2")},
+		{watch.Deleted, createBrokerWithConditions(name, corev1.ConditionTrue, corev1.ConditionTrue, "", "")},
+	}
+}
+
+func createBrokerWithConditions(name string, readyStatus corev1.ConditionStatus, otherReadyStatus corev1.ConditionStatus, reason string, message string) runtime.Object {
+	broker := newBroker(name)
+	broker.Status.Conditions = duckv1.Conditions([]apis.Condition{
+		{Type: "ChannelServiceReady", Status: otherReadyStatus},
+		{Type: apis.ConditionReady, Status: readyStatus, Reason: reason, Message: message},
+		{Type: "Addressable", Status: otherReadyStatus},
+	})
+	return broker
+}
diff --git a/pkg/kn/commands/broker/broker_test.go b/pkg/kn/commands/broker/broker_test.go
new file mode 100644
index 00000000..e2460656
--- /dev/null
+++ b/pkg/kn/commands/broker/broker_test.go
@@ -0,0 +1,79 @@
+/*
+Copyright 2020 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package broker
+
+import (
+	"bytes"
+
+	"k8s.io/client-go/tools/clientcmd"
+
+	clientv1beta1 "knative.dev/client/pkg/eventing/v1beta1"
+	"knative.dev/client/pkg/kn/commands"
+	v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1"
+)
+
+// Helper methods
+var blankConfig clientcmd.ClientConfig
+
+func init() {
+	var err error
+	blankConfig, err = clientcmd.NewClientConfigFromBytes([]byte(`kind: Config
+version: v1
+users:
+- name: u
+clusters:
+- name: c
+  cluster:
+    server: example.com
+contexts:
+- name: x
+  context:
+    user: u
+    cluster: c
+current-context: x
+`))
+	if err != nil {
+		panic(err)
+	}
+}
+
+func executeBrokerCommand(brokerClient clientv1beta1.KnEventingClient, args ...string) (string, error) {
+	knParams := &commands.KnParams{}
+	knParams.ClientConfig = blankConfig
+
+	output := new(bytes.Buffer)
+	knParams.Output = output
+
+	knParams.NewEventingClient = func(namespace string) (clientv1beta1.KnEventingClient, error) {
+		return brokerClient, nil
+	}
+
+	cmd := NewBrokerCommand(knParams)
+	cmd.SetArgs(args)
+	cmd.SetOutput(output)
+
+	err := cmd.Execute()
+
+	return output.String(), err
+}
+
+func createBroker(brokerName string) *v1beta1.Broker {
+	return clientv1beta1.NewBrokerBuilder(brokerName).Namespace("default").Build()
+}
+
+func createBrokerWithNamespace(brokerName, namespace string) *v1beta1.Broker {
+	return clientv1beta1.NewBrokerBuilder(brokerName).Namespace(namespace).Build()
+}
diff --git a/pkg/kn/commands/broker/create_test.go b/pkg/kn/commands/broker/create_test.go
new file mode 100644
index 00000000..a45fdd5b
--- /dev/null
+++ b/pkg/kn/commands/broker/create_test.go
@@ -0,0 +1,51 @@
+/*
+Copyright 2020 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package broker
+
+import (
+	"testing"
+
+	"gotest.tools/assert"
+
+	clienteventingv1beta1 "knative.dev/client/pkg/eventing/v1beta1"
+	"knative.dev/client/pkg/util"
+)
+
+var (
+	brokerName = "foo"
+)
+
+func TestBrokerCreate(t *testing.T) {
+	eventingClient := clienteventingv1beta1.NewMockKnEventingClient(t)
+
+	eventingRecorder := eventingClient.Recorder()
+	eventingRecorder.CreateBroker(createBroker(brokerName), nil)
+
+	out, err := executeBrokerCommand(eventingClient, "create", brokerName)
+	assert.NilError(t, err, "Broker should be created")
+	util.ContainsAll(out, "Broker", brokerName, "created", "namespace", "default")
+
+	eventingRecorder.Validate()
+}
+
+func TestBrokerCreateWithError(t *testing.T) {
+	eventingClient := clienteventingv1beta1.NewMockKnEventingClient(t)
+
+	_, err := executeBrokerCommand(eventingClient, "create")
+	assert.ErrorContains(t, err, "broker create")
+	util.ContainsAll(err.Error(), "broker create", "requires", "name", "argument")
+}
diff --git a/pkg/kn/commands/broker/delete_test.go b/pkg/kn/commands/broker/delete_test.go
new file mode 100644
index 00000000..a3ffc401
--- /dev/null
+++ b/pkg/kn/commands/broker/delete_test.go
@@ -0,0 +1,56 @@
+/*
+Copyright 2020 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package broker
+
+import (
+	"fmt"
+	"testing"
+
+	"gotest.tools/assert"
+
+	clienteventingv1beta1 "knative.dev/client/pkg/eventing/v1beta1"
+	"knative.dev/client/pkg/util"
+	"knative.dev/client/pkg/util/mock"
+)
+
+func TestBrokerDelete(t *testing.T) {
+	brokerName := "foo"
+	eventingClient := clienteventingv1beta1.NewMockKnEventingClient(t)
+
+	eventingRecorder := eventingClient.Recorder()
+	eventingRecorder.DeleteBroker(brokerName, mock.Any(), nil)
+
+	out, err := executeBrokerCommand(eventingClient, "delete", brokerName)
+	assert.NilError(t, err, "Broker should be deleted")
+	util.ContainsAll(out, "Broker", brokerName, "deleted", "namespace", "default")
+
+	eventingRecorder.Validate()
+}
+
+func TestBrokerWithDelete(t *testing.T) {
+	brokerName := "foo"
+	eventingClient := clienteventingv1beta1.NewMockKnEventingClient(t)
+
+	eventingRecorder := eventingClient.Recorder()
+	eventingRecorder.DeleteBroker(brokerName, mock.Any(), fmt.Errorf("broker %s not found", brokerName))
+
+	out, err := executeBrokerCommand(eventingClient, "delete", brokerName)
+	assert.ErrorContains(t, err, brokerName)
+	util.ContainsAll(out, "broker", brokerName, "not found")
+
+	eventingRecorder.Validate()
+}
diff --git a/pkg/kn/commands/broker/describe_test.go b/pkg/kn/commands/broker/describe_test.go
new file mode 100644
index 00000000..b37760f1
--- /dev/null
+++ b/pkg/kn/commands/broker/describe_test.go
@@ -0,0 +1,97 @@
+/*
+Copyright 2020 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package broker
+
+import (
+	"errors"
+	"strings"
+	"testing"
+
+	"gotest.tools/assert"
+	"gotest.tools/assert/cmp"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1"
+	"knative.dev/pkg/apis"
+	duckv1 "knative.dev/pkg/apis/duck/v1"
+
+	clientv1beta1 "knative.dev/client/pkg/eventing/v1beta1"
+	"knative.dev/client/pkg/util"
+)
+
+func TestBrokerDescribe(t *testing.T) {
+	client := clientv1beta1.NewMockKnEventingClient(t, "mynamespace")
+
+	recorder := client.Recorder()
+	recorder.GetBroker("foo", getBroker(), nil)
+
+	out, err := executeBrokerCommand(client, "describe", "foo")
+	assert.NilError(t, err)
+
+	assert.Assert(t, cmp.Regexp("Name:\\s+foo", out))
+	assert.Assert(t, cmp.Regexp("Namespace:\\s+default", out))
+
+	assert.Assert(t, util.ContainsAll(out, "Address:", "URL:", "http://foo-broker.test"))
+	assert.Assert(t, util.ContainsAll(out, "Conditions:", "Ready"))
+
+	// There're 2 empty lines used in the "describe" formatting
+	lineCounter := 0
+	for _, line := range strings.Split(strings.TrimSpace(out), "\n") {
+		if line == "" {
+			lineCounter++
+		}
+	}
+	assert.Equal(t, lineCounter, 2)
+
+	// Validate that all recorded API methods have been called
+	recorder.Validate()
+}
+
+func TestDescribeError(t *testing.T) {
+	client := clientv1beta1.NewMockKnEventingClient(t, "mynamespace")
+
+	recorder := client.Recorder()
+	recorder.GetBroker("foo", nil, errors.New("brokers.eventing.knative.dev 'foo' not found"))
+
+	_, err := executeBrokerCommand(client, "describe", "foo")
+	assert.ErrorContains(t, err, "foo", "not found")
+
+	recorder.Validate()
+}
+
+func getBroker() *v1beta1.Broker {
+	return &v1beta1.Broker{
+		TypeMeta: v1.TypeMeta{},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "foo",
+			Namespace: "default",
+		},
+		Status: v1beta1.BrokerStatus{
+			Address: duckv1.Addressable{
+				URL: &apis.URL{Scheme: "http", Host: "foo-broker.test"},
+			},
+			Status: duckv1.Status{
+				Conditions: duckv1.Conditions{
+					apis.Condition{
+						Type:   "Ready",
+						Status: "True",
+					},
+				},
+			},
+		},
+	}
+}
diff --git a/pkg/kn/commands/broker/list_test.go b/pkg/kn/commands/broker/list_test.go
new file mode 100644
index 00000000..4a439cd4
--- /dev/null
+++ b/pkg/kn/commands/broker/list_test.go
@@ -0,0 +1,84 @@
+/*
+Copyright 2020 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package broker
+
+import (
+	"strings"
+	"testing"
+
+	"gotest.tools/assert"
+
+	clienteventingv1beta1 "knative.dev/client/pkg/eventing/v1beta1"
+	"knative.dev/client/pkg/util"
+	v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1"
+)
+
+func TestBrokerList(t *testing.T) {
+	eventingClient := clienteventingv1beta1.NewMockKnEventingClient(t)
+	eventingRecorder := eventingClient.Recorder()
+
+	broker1 := createBroker("foo1")
+	broker2 := createBroker("foo2")
+	broker3 := createBroker("foo3")
+	brokerList := &v1beta1.BrokerList{Items: []v1beta1.Broker{*broker1, *broker2, *broker3}}
+	eventingRecorder.ListBrokers(brokerList, nil)
+
+	output, err := executeBrokerCommand(eventingClient, "list")
+	assert.NilError(t, err)
+
+	outputLines := strings.Split(output, "\n")
+	assert.Check(t, util.ContainsAll(outputLines[0], "NAME", "URL", "AGE", "CONDITIONS", "READY", "REASON"))
+	assert.Check(t, util.ContainsAll(outputLines[1], "foo1"))
+	assert.Check(t, util.ContainsAll(outputLines[2], "foo2"))
+	assert.Check(t, util.ContainsAll(outputLines[3], "foo3"))
+
+	eventingRecorder.Validate()
+}
+
+func TestBrokerListEmpty(t *testing.T) {
+	eventingClient := clienteventingv1beta1.NewMockKnEventingClient(t)
+	eventingRecorder := eventingClient.Recorder()
+
+	eventingRecorder.ListBrokers(&v1beta1.BrokerList{}, nil)
+	output, err := executeBrokerCommand(eventingClient, "list")
+	assert.NilError(t, err)
+	assert.Assert(t, util.ContainsAll(output, "No", "brokers", "found"))
+
+	eventingRecorder.Validate()
+}
+
+func TestTriggerListAllNamespace(t *testing.T) {
+	eventingClient := clienteventingv1beta1.NewMockKnEventingClient(t)
+	eventingRecorder := eventingClient.Recorder()
+
+	broker1 := createBrokerWithNamespace("foo1", "default1")
+	broker2 := createBrokerWithNamespace("foo2", "default2")
+	broker3 := createBrokerWithNamespace("foo3", "default3")
+	brokerList := &v1beta1.BrokerList{Items: []v1beta1.Broker{*broker1, *broker2, *broker3}}
+	eventingRecorder.ListBrokers(brokerList, nil)
+
+	output, err := executeBrokerCommand(eventingClient, "list", "--all-namespaces")
+	assert.NilError(t, err)
+
+	outputLines := strings.Split(output, "\n")
+	assert.Check(t, util.ContainsAll(outputLines[0], "NAMESPACE", "NAME", "AGE", "CONDITIONS", "READY", "REASON"))
+	assert.Check(t, util.ContainsAll(outputLines[1], "default1", "foo1"))
+	assert.Check(t, util.ContainsAll(outputLines[2], "default2", "foo2"))
+	assert.Check(t, util.ContainsAll(outputLines[3], "default3", "foo3"))
+
+	eventingRecorder.Validate()
+}
diff --git a/pkg/kn/commands/source/apiserver/apiserver_test.go b/pkg/kn/commands/source/apiserver/apiserver_test.go
index 0f7d21c9..7cd1bc4a 100644
--- a/pkg/kn/commands/source/apiserver/apiserver_test.go
+++ b/pkg/kn/commands/source/apiserver/apiserver_test.go
@@ -29,7 +29,6 @@ import (
 
 const testNamespace = "default"
 
-// Helper methods
 var blankConfig clientcmd.ClientConfig
 
 // TODO: Remove that blankConfig hack for tests in favor of overwriting GetConfig()
@@ -83,20 +82,12 @@ func cleanupAPIServerMockClient() {
 	apiServerSourceClientFactory = nil
 }
 
-func createAPIServerSource(name, resourceKind, resourceVersion, serviceAccount, mode, service string, ceOverrides map[string]string) *v1alpha2.ApiServerSource {
+func createAPIServerSource(name, resourceKind, resourceVersion, serviceAccount, mode string, ceOverrides map[string]string, sink duckv1.Destination) *v1alpha2.ApiServerSource {
 	resources := []v1alpha2.APIVersionKindSelector{{
 		APIVersion: resourceVersion,
 		Kind:       resourceKind,
 	}}
 
-	sink := duckv1.Destination{
-		Ref: &duckv1.KReference{
-			Kind:       "Service",
-			Name:       service,
-			APIVersion: "serving.knative.dev/v1",
-			Namespace:  "default",
-		}}
-
 	return clientv1alpha2.NewAPIServerSourceBuilder(name).
 		Resources(resources).
 		ServiceAccount(serviceAccount).
@@ -105,3 +96,14 @@ func createAPIServerSource(name, resourceKind, resourceVersion, serviceAccount,
 		CloudEventOverrides(ceOverrides, []string{}).
 		Build()
 }
+
+func createSinkv1(serviceName, namespace string) duckv1.Destination {
+	return duckv1.Destination{
+		Ref: &duckv1.KReference{
+			Kind:       "Service",
+			Name:       serviceName,
+			APIVersion: "serving.knative.dev/v1",
+			Namespace:  namespace,
+		},
+	}
+}
diff --git a/pkg/kn/commands/source/apiserver/create_test.go b/pkg/kn/commands/source/apiserver/create_test.go
index e0bc78d4..505af134 100644
--- a/pkg/kn/commands/source/apiserver/create_test.go
+++ b/pkg/kn/commands/source/apiserver/create_test.go
@@ -35,11 +35,11 @@ func TestCreateApiServerSource(t *testing.T) {
 	apiServerClient := v1alpha2.NewMockKnAPIServerSourceClient(t)
 
 	apiServerRecorder := apiServerClient.Recorder()
-	apiServerRecorder.CreateAPIServerSource(createAPIServerSource("testsource", "Event", "v1", "testsa", "Reference", "testsvc", map[string]string{"bla": "blub", "foo": "bar"}), nil)
+	apiServerRecorder.CreateAPIServerSource(createAPIServerSource("testsource", "Event", "v1", "testsa", "Reference", map[string]string{"bla": "blub", "foo": "bar"}, createSinkv1("testsvc", "default")), nil)
 
 	out, err := executeAPIServerSourceCommand(apiServerClient, dynamicClient, "create", "testsource", "--resource", "Event:v1", "--service-account", "testsa", "--sink", "svc:testsvc", "--mode", "Reference", "--ce-override", "bla=blub", "--ce-override", "foo=bar")
 	assert.NilError(t, err, "ApiServer source should be created")
-	util.ContainsAll(out, "created", "default", "testsource")
+	assert.Assert(t, util.ContainsAll(out, "created", "default", "testsource"))
 
 	apiServerRecorder.Validate()
 }
diff --git a/pkg/kn/commands/source/apiserver/delete_test.go b/pkg/kn/commands/source/apiserver/delete_test.go
index cc91235b..6a5c3076 100644
--- a/pkg/kn/commands/source/apiserver/delete_test.go
+++ b/pkg/kn/commands/source/apiserver/delete_test.go
@@ -33,7 +33,7 @@ func TestApiServerSourceDelete(t *testing.T) {
 
 	out, err := executeAPIServerSourceCommand(apiServerClient, nil, "delete", "testsource")
 	assert.NilError(t, err)
-	util.ContainsAll(out, "deleted", "testns", "testsource")
+	assert.Assert(t, util.ContainsAll(out, "deleted", "default", "testsource"))
 
 	apiServerRecorder.Validate()
 }
@@ -47,7 +47,7 @@ func TestDeleteWithError(t *testing.T) {
 
 	out, err := executeAPIServerSourceCommand(apiServerClient, nil, "delete", "testsource")
 	assert.ErrorContains(t, err, "testsource")
-	util.ContainsAll(out, "apiserver", "source", "testsource", "not found")
+	assert.Assert(t, util.ContainsAll(out, "apiserver", "source", "testsource", "not found"))
 
 	apiServerRecorder.Validate()
 }
diff --git a/pkg/kn/commands/source/apiserver/describe_test.go b/pkg/kn/commands/source/apiserver/describe_test.go
index 8abfca2e..bf61cae7 100644
--- a/pkg/kn/commands/source/apiserver/describe_test.go
+++ b/pkg/kn/commands/source/apiserver/describe_test.go
@@ -22,18 +22,30 @@ import (
 
 	"knative.dev/client/pkg/sources/v1alpha2"
 	"knative.dev/client/pkg/util"
+	"knative.dev/pkg/apis"
+	duckv1 "knative.dev/pkg/apis/duck/v1"
+)
+
+var (
+	sinkURI = duckv1.Destination{
+		URI: &apis.URL{
+			Scheme: "https",
+			Host:   "foo",
+		}}
 )
 
 func TestSimpleDescribe(t *testing.T) {
 	apiServerClient := v1alpha2.NewMockKnAPIServerSourceClient(t, "mynamespace")
 
 	apiServerRecorder := apiServerClient.Recorder()
-	sampleSource := createAPIServerSource("testsource", "Event", "v1", "testsa", "Reference", "testsvc", map[string]string{"foo": "bar"})
+	sampleSource := createAPIServerSource("testsource", "Event", "v1", "testsa", "Reference", map[string]string{"foo": "bar"}, createSinkv1("testsvc", "default"))
+	sampleSource.Namespace = "mynamespace"
 	apiServerRecorder.GetAPIServerSource("testsource", sampleSource, nil)
 
 	out, err := executeAPIServerSourceCommand(apiServerClient, nil, "describe", "testsource")
 	assert.NilError(t, err)
-	util.ContainsAll(out, "testsource", "testsa", "Reference", "testsvc", "Service", "Resources", "Event", "v1", "false", "Conditions", "foo", "bar")
+	assert.Assert(t, util.ContainsAll(out, "testsource", "testsa", "Reference", "testsvc", "Service (serving.knative.dev/v1)", "Resources", "Event", "v1", "Conditions", "foo", "bar", "mynamespace", "default"))
+	assert.Assert(t, util.ContainsNone(out, "URI"))
 
 	apiServerRecorder.Validate()
 }
@@ -46,7 +58,22 @@ func TestDescribeError(t *testing.T) {
 
 	out, err := executeAPIServerSourceCommand(apiServerClient, nil, "describe", "testsource")
 	assert.ErrorContains(t, err, "testsource")
-	util.ContainsAll(out, "Usage", "testsource")
+	assert.Assert(t, util.ContainsAll(out, "Usage", "testsource"))
+
+	apiServerRecorder.Validate()
+}
+
+func TestDescribeWithSinkURI(t *testing.T) {
+	apiServerClient := v1alpha2.NewMockKnAPIServerSourceClient(t, "mynamespace")
+
+	apiServerRecorder := apiServerClient.Recorder()
+	sampleSource := createAPIServerSource("testsource", "Event", "v1", "testsa", "Reference", map[string]string{"foo": "bar"}, sinkURI)
+	sampleSource.Namespace = "mynamespace"
+	apiServerRecorder.GetAPIServerSource("testsource", sampleSource, nil)
+
+	out, err := executeAPIServerSourceCommand(apiServerClient, nil, "describe", "testsource")
+	assert.NilError(t, err)
+	assert.Assert(t, util.ContainsAll(out, "testsource", "testsa", "Reference", "Resources", "Event", "v1", "Conditions", "foo", "bar", "URI", "https", "foo", "mynamespace"))
 
 	apiServerRecorder.Validate()
 }
diff --git a/pkg/kn/commands/source/apiserver/list_test.go b/pkg/kn/commands/source/apiserver/list_test.go
index fbc63ad6..4e22247a 100644
--- a/pkg/kn/commands/source/apiserver/list_test.go
+++ b/pkg/kn/commands/source/apiserver/list_test.go
@@ -29,7 +29,7 @@ func TestListAPIServerSource(t *testing.T) {
 	apiServerClient := v1alpha22.NewMockKnAPIServerSourceClient(t)
 
 	apiServerRecorder := apiServerClient.Recorder()
-	sampleSource := createAPIServerSource("testsource", "Event", "v1", "testsa", "Reference", "testsvc", nil)
+	sampleSource := createAPIServerSource("testsource", "Event", "v1", "testsa", "Reference", nil, createSinkv1("testsvc", "default"))
 	sampleSourceList := v1alpha2.ApiServerSourceList{}
 	sampleSourceList.Items = []v1alpha2.ApiServerSource{*sampleSource}
 
@@ -37,8 +37,8 @@ func TestListAPIServerSource(t *testing.T) {
 
 	out, err := executeAPIServerSourceCommand(apiServerClient, nil, "list")
 	assert.NilError(t, err, "sources should be listed")
-	util.ContainsAll(out, "NAME", "RESOURCES", "SINK", "AGE", "CONDITIONS", "READY", "REASON")
-	util.ContainsAll(out, "testsource", "Eventing:v1:false", "mysvc")
+	assert.Assert(t, util.ContainsAll(out, "NAME", "RESOURCES", "SINK", "AGE", "CONDITIONS", "READY", "REASON"))
+	assert.Assert(t, util.ContainsAll(out, "testsource", "Event:v1", "svc:testsvc"))
 
 	apiServerRecorder.Validate()
 }
@@ -53,8 +53,8 @@ func TestListAPIServerSourceEmpty(t *testing.T) {
 
 	out, err := executeAPIServerSourceCommand(apiServerClient, nil, "list")
 	assert.NilError(t, err, "Sources should be listed")
-	util.ContainsNone(out, "NAME", "RESOURCES", "SINK", "AGE", "CONDITIONS", "READY", "REASON")
-	util.ContainsAll(out, "No", "ApiServer", "source", "found")
+	assert.Assert(t, util.ContainsNone(out, "NAME", "RESOURCES", "SINK", "AGE", "CONDITIONS", "READY", "REASON"))
+	assert.Assert(t, util.ContainsAll(out, "No", "ApiServer", "source", "found"))
 
 	apiServerRecorder.Validate()
 }
diff --git a/pkg/kn/commands/source/apiserver/update_test.go b/pkg/kn/commands/source/apiserver/update_test.go
index 7dac5589..ee95dcdc 100644
--- a/pkg/kn/commands/source/apiserver/update_test.go
+++ b/pkg/kn/commands/source/apiserver/update_test.go
@@ -37,10 +37,10 @@ func TestApiServerSourceUpdate(t *testing.T) {
 
 	apiServerRecorder := apiServerClient.Recorder()
 
-	present := createAPIServerSource("testsource", "Event", "v1", "testsa1", "Reference", "svc1", map[string]string{"bla": "blub", "foo": "bar"})
+	present := createAPIServerSource("testsource", "Event", "v1", "testsa1", "Reference", map[string]string{"bla": "blub", "foo": "bar"}, createSinkv1("svc1", "default"))
 	apiServerRecorder.GetAPIServerSource("testsource", present, nil)
 
-	updated := createAPIServerSource("testsource", "Event", "v1", "testsa2", "Reference", "svc2", map[string]string{"foo": "baz"})
+	updated := createAPIServerSource("testsource", "Event", "v1", "testsa2", "Reference", map[string]string{"foo": "baz"}, createSinkv1("svc2", "default"))
 	apiServerRecorder.UpdateAPIServerSource(updated, nil)
 
 	output, err := executeAPIServerSourceCommand(apiServerClient, dynamicClient, "update", "testsource", "--service-account", "testsa2", "--sink", "svc:svc2", "--ce-override", "bla-", "--ce-override", "foo=baz")
@@ -54,7 +54,7 @@ func TestApiServerSourceUpdateDeletionTimestampNotNil(t *testing.T) {
 	apiServerClient := v1alpha2.NewMockKnAPIServerSourceClient(t)
 	apiServerRecorder := apiServerClient.Recorder()
 
-	present := createAPIServerSource("testsource", "Event", "v1", "testsa1", "Ref", "svc1", nil)
+	present := createAPIServerSource("testsource", "Event", "v1", "testsa1", "Ref", nil, createSinkv1("svc1", "default"))
 	present.DeletionTimestamp = &metav1.Time{Time: time.Now()}
 	apiServerRecorder.GetAPIServerSource("testsource", present, nil)
 
diff --git a/pkg/kn/commands/source/binding/binding_test.go b/pkg/kn/commands/source/binding/binding_test.go
index 2c814048..559c2b75 100644
--- a/pkg/kn/commands/source/binding/binding_test.go
+++ b/pkg/kn/commands/source/binding/binding_test.go
@@ -85,8 +85,8 @@ func cleanupSinkBindingClient() {
 	sinkBindingClientFactory = nil
 }
 
-func createSinkBinding(name, service string, subjectGvk schema.GroupVersionKind, subjectName string, ceOverrides map[string]string) *v1alpha2.SinkBinding {
-	sink := createServiceSink(service)
+func createSinkBinding(name, service string, subjectGvk schema.GroupVersionKind, subjectName, namespace string, ceOverrides map[string]string) *v1alpha2.SinkBinding {
+	sink := createServiceSink(service, namespace)
 	builder := clientv1alpha2.NewSinkBindingBuilder(name).
 		Namespace("default").
 		Sink(&sink).
@@ -99,8 +99,12 @@ func createSinkBinding(name, service string, subjectGvk schema.GroupVersionKind,
 	return binding
 }
 
-func createServiceSink(service string) v1.Destination {
+func createServiceSink(service, namespace string) v1.Destination {
 	return v1.Destination{
-		Ref: &v1.KReference{Name: service, Kind: "Service", APIVersion: "serving.knative.dev/v1", Namespace: "default"},
+		Ref: &v1.KReference{Name: service,
+			Kind:       "Service",
+			APIVersion: "serving.knative.dev/v1",
+			Namespace:  namespace,
+		},
 	}
 }
diff --git a/pkg/kn/commands/source/binding/create_test.go b/pkg/kn/commands/source/binding/create_test.go
index 6a2f92f2..8bdbe556 100644
--- a/pkg/kn/commands/source/binding/create_test.go
+++ b/pkg/kn/commands/source/binding/create_test.go
@@ -31,11 +31,11 @@ func TestSimpleCreateBinding(t *testing.T) {
 
 	bindingClient := v1alpha2.NewMockKnSinkBindingClient(t)
 	bindingRecorder := bindingClient.Recorder()
-	bindingRecorder.CreateSinkBinding(createSinkBinding("testbinding", "mysvc", deploymentGvk, "mydeploy", map[string]string{"bla": "blub", "foo": "bar"}), nil)
+	bindingRecorder.CreateSinkBinding(createSinkBinding("testbinding", "mysvc", deploymentGvk, "mydeploy", "default", map[string]string{"bla": "blub", "foo": "bar"}), nil)
 
 	out, err := executeSinkBindingCommand(bindingClient, dynamicClient, "create", "testbinding", "--sink", "svc:mysvc", "--subject", "deployment:apps/v1:mydeploy", "--ce-override", "bla=blub", "--ce-override", "foo=bar")
 	assert.NilError(t, err, "Source should have been created")
-	util.ContainsAll(out, "created", "default", "testbinding")
+	assert.Assert(t, util.ContainsAll(out, "created", "default", "testbinding"))
 
 	bindingRecorder.Validate()
 }
diff --git a/pkg/kn/commands/source/binding/delete_test.go b/pkg/kn/commands/source/binding/delete_test.go
index 277d7c57..42bec6b7 100644
--- a/pkg/kn/commands/source/binding/delete_test.go
+++ b/pkg/kn/commands/source/binding/delete_test.go
@@ -33,7 +33,7 @@ func TestSimpleDelete(t *testing.T) {
 
 	out, err := executeSinkBindingCommand(bindingClient, nil, "delete", "mybinding")
 	assert.NilError(t, err)
-	util.ContainsAll(out, "deleted", "mynamespace", "mybinding", "sink binding")
+	assert.Assert(t, util.ContainsAll(out, "deleted", "mynamespace", "mybinding", "Sink binding"))
 
 	bindingRecorder.Validate()
 }
@@ -47,7 +47,7 @@ func TestDeleteWithError(t *testing.T) {
 
 	out, err := executeSinkBindingCommand(bindingClient, nil, "delete", "mybinding")
 	assert.ErrorContains(t, err, "mybinding")
-	util.ContainsAll(out, "no such", "mybinding")
+	assert.Assert(t, util.ContainsAll(out, "no such", "mybinding"))
 
 	bindingRecorder.Validate()
 }
diff --git a/pkg/kn/commands/source/binding/describe_test.go b/pkg/kn/commands/source/binding/describe_test.go
index 5c8496d3..15576435 100644
--- a/pkg/kn/commands/source/binding/describe_test.go
+++ b/pkg/kn/commands/source/binding/describe_test.go
@@ -22,6 +22,7 @@ import (
 	"gotest.tools/assert"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2"
+	"knative.dev/pkg/apis"
 	duckv1 "knative.dev/pkg/apis/duck/v1"
 	"knative.dev/pkg/apis/duck/v1alpha1"
 	"knative.dev/pkg/tracker"
@@ -30,15 +31,24 @@ import (
 	"knative.dev/client/pkg/util"
 )
 
+var (
+	sinkURI = duckv1.Destination{
+		URI: &apis.URL{
+			Scheme: "https",
+			Host:   "foo",
+		}}
+)
+
 func TestSimpleDescribeWitName(t *testing.T) {
 	bindingClient := clientv1alpha2.NewMockKnSinkBindingClient(t, "mynamespace")
 
 	bindingRecorder := bindingClient.Recorder()
-	bindingRecorder.GetSinkBinding("mybinding", getSinkBindingSource("myapp", map[string]string{"foo": "bar"}), nil)
+	bindingRecorder.GetSinkBinding("mybinding", getSinkBindingSource("myapp", map[string]string{"foo": "bar"}, createServiceSink("mysvc", "myservicenamespace")), nil)
 
 	out, err := executeSinkBindingCommand(bindingClient, nil, "describe", "mybinding")
 	assert.NilError(t, err)
-	util.ContainsAll(out, "mybinding", "myapp", "Deployment", "app/v1", "mynamespace", "mysvc", "foo", "bar")
+	assert.Assert(t, util.ContainsAll(out, "mysinkbinding", "myapp", "Deployment", "apps/v1", "mynamespace", "mysvc", "foo", "bar", "myservicenamespace", "Service (serving.knative.dev/v1)"))
+	assert.Assert(t, util.ContainsNone(out, "URI"))
 
 	bindingRecorder.Validate()
 }
@@ -47,11 +57,12 @@ func TestSimpleDescribeWithSelector(t *testing.T) {
 	bindingClient := clientv1alpha2.NewMockKnSinkBindingClient(t, "mynamespace")
 
 	bindingRecorder := bindingClient.Recorder()
-	bindingRecorder.GetSinkBinding("mybinding", getSinkBindingSource("app=myapp,type=test", nil), nil)
+	bindingRecorder.GetSinkBinding("mybinding", getSinkBindingSource("app=myapp,type=test", nil, createServiceSink("mysvc", "myservicenamespace")), nil)
 
 	out, err := executeSinkBindingCommand(bindingClient, nil, "describe", "mybinding")
 	assert.NilError(t, err)
-	util.ContainsAll(out, "mybinding", "app:", "myapp", "type:", "test", "Deployment", "app/v1", "mynamespace", "mysvc")
+	assert.Assert(t, util.ContainsAll(out, "mysinkbinding", "app:", "myapp", "type:", "test", "Deployment", "apps/v1", "mynamespace", "mysvc", "myservicenamespace", "Service (serving.knative.dev/v1)"))
+	assert.Assert(t, util.ContainsNone(out, "URI"))
 
 	bindingRecorder.Validate()
 }
@@ -64,12 +75,25 @@ func TestDescribeError(t *testing.T) {
 
 	out, err := executeSinkBindingCommand(bindingClient, nil, "describe", "mybinding")
 	assert.ErrorContains(t, err, "mybinding")
-	util.ContainsAll(out, "mybinding")
+	assert.Assert(t, util.ContainsAll(out, "mybinding"))
 
 	bindingRecorder.Validate()
 }
 
-func getSinkBindingSource(nameOrSelector string, ceOverrides map[string]string) *v1alpha2.SinkBinding {
+func TestDescribeWithSinkURI(t *testing.T) {
+	bindingClient := clientv1alpha2.NewMockKnSinkBindingClient(t, "mynamespace")
+
+	bindingRecorder := bindingClient.Recorder()
+	bindingRecorder.GetSinkBinding("mybinding", getSinkBindingSource("myapp", map[string]string{"foo": "bar"}, sinkURI), nil)
+
+	out, err := executeSinkBindingCommand(bindingClient, nil, "describe", "mybinding")
+	assert.NilError(t, err)
+	assert.Assert(t, util.ContainsAll(out, "mysinkbinding", "myapp", "Deployment", "apps/v1", "mynamespace", "foo", "bar", "URI", "https", "foo"))
+
+	bindingRecorder.Validate()
+}
+
+func getSinkBindingSource(nameOrSelector string, ceOverrides map[string]string, sink duckv1.Destination) *v1alpha2.SinkBinding {
 	binding := &v1alpha2.SinkBinding{
 		TypeMeta: v1.TypeMeta{},
 		ObjectMeta: v1.ObjectMeta{
@@ -77,13 +101,7 @@ func getSinkBindingSource(nameOrSelector string, ceOverrides map[string]string)
 		},
 		Spec: v1alpha2.SinkBindingSpec{
 			SourceSpec: duckv1.SourceSpec{
-				Sink: duckv1.Destination{
-					Ref: &duckv1.KReference{
-						Kind:      "Service",
-						Namespace: "myservicenamespace",
-						Name:      "mysvc",
-					},
-				},
+				Sink: sink,
 			},
 			BindingSpec: v1alpha1.BindingSpec{
 				Subject: tracker.Reference{
diff --git a/pkg/kn/commands/source/binding/list_test.go b/pkg/kn/commands/source/binding/list_test.go
index 28f75912..b11d385b 100644
--- a/pkg/kn/commands/source/binding/list_test.go
+++ b/pkg/kn/commands/source/binding/list_test.go
@@ -28,7 +28,7 @@ func TestListBindingSimple(t *testing.T) {
 	bindingClient := clientv1alpha2.NewMockKnSinkBindingClient(t)
 
 	bindingRecorder := bindingClient.Recorder()
-	binding := createSinkBinding("testbinding", "mysvc", deploymentGvk, "mydeploy", nil)
+	binding := createSinkBinding("testbinding", "mysvc", deploymentGvk, "mydeploy", "default", nil)
 	bindingList := v1alpha2.SinkBindingList{
 		Items: []v1alpha2.SinkBinding{
 			*binding,
@@ -38,8 +38,8 @@ func TestListBindingSimple(t *testing.T) {
 
 	out, err := executeSinkBindingCommand(bindingClient, nil, "list")
 	assert.NilError(t, err, "Sources should be listed")
-	util.ContainsAll(out, "NAME", "SUBJECT", "SINK", "AGE", "CONDITIONS", "READY", "REASON")
-	util.ContainsAll(out, "testbinding", "deployment:apps/v1:mydeploy", "mysvc")
+	assert.Assert(t, util.ContainsAll(out, "NAME", "SUBJECT", "SINK", "AGE", "CONDITIONS", "READY", "REASON"))
+	assert.Assert(t, util.ContainsAll(out, "testbinding", "deployment:apps/v1:mydeploy", "mysvc"))
 
 	bindingRecorder.Validate()
 }
@@ -53,8 +53,8 @@ func TestListBindingEmpty(t *testing.T) {
 
 	out, err := executeSinkBindingCommand(bindingClient, nil, "list")
 	assert.NilError(t, err, "Sources should be listed")
-	util.ContainsNone(out, "NAME", "SUBJECT", "SINK", "AGE", "CONDITIONS", "READY", "REASON")
-	util.ContainsAll(out, "No", "sink binding", "found")
+	assert.Assert(t, util.ContainsNone(out, "NAME", "SUBJECT", "SINK", "AGE", "CONDITIONS", "READY", "REASON"))
+	assert.Assert(t, util.ContainsAll(out, "No", "sink binding", "found"))
 
 	bindingRecorder.Validate()
 }
diff --git a/pkg/kn/commands/source/binding/update_test.go b/pkg/kn/commands/source/binding/update_test.go
index ccf63a03..dc303ab6 100644
--- a/pkg/kn/commands/source/binding/update_test.go
+++ b/pkg/kn/commands/source/binding/update_test.go
@@ -39,12 +39,12 @@ func TestSimpleBindingUpdate(t *testing.T) {
 	bindingRecorder := sinkBindingClient.Recorder()
 	ceOverrideMap := map[string]string{"bla": "blub", "foo": "bar"}
 	ceOverrideMapUpdated := map[string]string{"foo": "baz", "new": "ceoverride"}
-	bindingRecorder.GetSinkBinding("testbinding", createSinkBinding("testbinding", "mysvc", deploymentGvk, "mydeploy", ceOverrideMap), nil)
-	bindingRecorder.UpdateSinkBinding(createSinkBinding("testbinding", "othersvc", deploymentGvk, "mydeploy", ceOverrideMapUpdated), nil)
+	bindingRecorder.GetSinkBinding("testbinding", createSinkBinding("testbinding", "mysvc", deploymentGvk, "mydeploy", "default", ceOverrideMap), nil)
+	bindingRecorder.UpdateSinkBinding(createSinkBinding("testbinding", "othersvc", deploymentGvk, "mydeploy", "default", ceOverrideMapUpdated), nil)
 
 	out, err := executeSinkBindingCommand(sinkBindingClient, dynamicClient, "update", "testbinding", "--sink", "svc:othersvc", "--ce-override", "bla-", "--ce-override", "foo=baz", "--ce-override", "new=ceoverride")
 	assert.NilError(t, err)
-	util.ContainsAll(out, "updated", "default", "testbinding", "foo", "bar")
+	assert.Assert(t, util.ContainsAll(out, "updated", "default", "testbinding"))
 
 	bindingRecorder.Validate()
 }
@@ -64,7 +64,7 @@ func TestUpdateError(t *testing.T) {
 
 	out, err := executeSinkBindingCommand(sinkBindingClient, nil, "update", "testbinding")
 	assert.ErrorContains(t, err, "testbinding")
-	util.ContainsAll(out, "testbinding", "name", "required")
+	assert.Assert(t, util.ContainsAll(out, "Error:", "testbinding", "no", "binding"))
 
 	bindingRecorder.Validate()
 }
@@ -72,7 +72,7 @@ func TestUpdateError(t *testing.T) {
 func TestBindingUpdateDeletionTimestampNotNil(t *testing.T) {
 	sinkBindingClient := clientsourcesv1alpha1.NewMockKnSinkBindingClient(t)
 	bindingRecorder := sinkBindingClient.Recorder()
-	present := createSinkBinding("testbinding", "", deploymentGvk, "", nil)
+	present := createSinkBinding("testbinding", "", deploymentGvk, "", "default", nil)
 	present.DeletionTimestamp = &v1.Time{Time: time.Now()}
 	bindingRecorder.GetSinkBinding("testbinding", present, nil)
 
diff --git a/test/e2e/broker_test.go b/test/e2e/broker_test.go
new file mode 100644
index 00000000..525efb16
--- /dev/null
+++ b/test/e2e/broker_test.go
@@ -0,0 +1,104 @@
+// Copyright 2020 The Knative Authors
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build e2e
+// +build !serving
+
+package e2e
+
+import (
+	"testing"
+
+	"gotest.tools/assert"
+
+	"knative.dev/client/lib/test"
+	"knative.dev/client/pkg/util"
+)
+
+func TestBroker(t *testing.T) {
+	t.Parallel()
+	it, err := test.NewKnTest()
+	assert.NilError(t, err)
+	defer func() {
+		assert.NilError(t, it.Teardown())
+	}()
+
+	r := test.NewKnRunResultCollector(t, it)
+	defer r.DumpIfFailed()
+
+	t.Log("create broker, list and describe it")
+	brokerCreate(r, "foo1")
+	verifyBrokerList(r, "foo1")
+	verifyBrokerListOutputName(r, "foo1")
+	verifyBrokerDescribe(r, "foo1")
+	brokerDelete(r, "foo1", false)
+
+	t.Log("create broker and delete it")
+	brokerCreate(r, "foo2")
+	verifyBrokerList(r, "foo2")
+	brokerDelete(r, "foo2", true)
+	verifyBrokerNotfound(r, "foo2")
+
+	t.Log("create multiple brokers and list them")
+	brokerCreate(r, "foo3")
+	brokerCreate(r, "foo4")
+	verifyBrokerList(r, "foo3", "foo4")
+	verifyBrokerListOutputName(r, "foo3", "foo4")
+	brokerDelete(r, "foo3", true)
+	brokerDelete(r, "foo4", true)
+	verifyBrokerNotfound(r, "foo3")
+	verifyBrokerNotfound(r, "foo4")
+}
+
+// Private functions
+
+func brokerCreate(r *test.KnRunResultCollector, name string) {
+	out := r.KnTest().Kn().Run("broker", "create", name)
+	r.AssertNoError(out)
+	assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "Broker", name, "created", "namespace", r.KnTest().Kn().Namespace()))
+}
+
+func brokerDelete(r *test.KnRunResultCollector, name string, wait bool) {
+	args := []string{"broker", "delete", name}
+	if wait {
+		args = append(args, "--wait")
+	}
+	out := r.KnTest().Kn().Run(args...)
+	r.AssertNoError(out)
+	assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "Broker", name, "deleted", "namespace", r.KnTest().Kn().Namespace()))
+}
+
+func verifyBrokerList(r *test.KnRunResultCollector, brokers ...string) {
+	out := r.KnTest().Kn().Run("broker", "list")
+	r.AssertNoError(out)
+	assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, brokers...))
+}
+
+func verifyBrokerListOutputName(r *test.KnRunResultCollector, broker ...string) {
+	out := r.KnTest().Kn().Run("broker", "list", "--output", "name")
+	r.AssertNoError(out)
+	assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, broker...))
+}
+
+func verifyBrokerDescribe(r *test.KnRunResultCollector, name string) {
+	out := r.KnTest().Kn().Run("broker", "describe", name)
+	r.AssertNoError(out)
+	assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, name, "Address:", "URL:", "Conditions:"))
+}
+
+func verifyBrokerNotfound(r *test.KnRunResultCollector, name string) {
+	out := r.KnTest().Kn().Run("broker", "describe", name)
+	r.AssertError(out)
+	assert.Check(r.T(), util.ContainsAll(out.Stderr, name, "not found"))
+}