diff --git a/cmd/burrow/main.go b/cmd/burrow/main.go
index fa38362061e30a01c4170f4d8b5081f7e9f1ccc7..d1fa13f458c656e2ee27d8a5b00f23116e397d4b 100644
--- a/cmd/burrow/main.go
+++ b/cmd/burrow/main.go
@@ -14,6 +14,7 @@ import (
 	"github.com/hyperledger/burrow/genesis"
 	"github.com/hyperledger/burrow/genesis/spec"
 	"github.com/hyperledger/burrow/keys"
+	"github.com/hyperledger/burrow/keys/mock"
 	"github.com/hyperledger/burrow/logging"
 	logging_config "github.com/hyperledger/burrow/logging/config"
 	"github.com/hyperledger/burrow/logging/config/presets"
@@ -79,6 +80,10 @@ func main() {
 			tomlOpt := cmd.BoolOpt("t toml", false, "Emit GenesisSpec as TOML rather than the "+
 				"default JSON")
 
+			baseOpt := cmd.StringsOpt("b base", nil, "Provide a base GenesisSpecs on top of which any "+
+				"additional GenesisSpec presets specified by other flags will be merged. GenesisSpecs appearing "+
+				"later take precedent over those appearing early if multiple --base flags are provided")
+
 			fullOpt := cmd.IntOpt("f full-accounts", 1, "Number of preset Full type accounts")
 			validatorOpt := cmd.IntOpt("v validator-accounts", 0, "Number of preset Validator type accounts")
 			rootOpt := cmd.IntOpt("r root-accounts", 0, "Number of preset Root type accounts")
@@ -86,10 +91,19 @@ func main() {
 			participantsOpt := cmd.IntOpt("p participant-accounts", 1, "Number of preset Participant type accounts")
 			chainNameOpt := cmd.StringOpt("n chain-name", "", "Default chain name")
 
-			cmd.Spec = "[--full-accounts] [--validator-accounts] [--root-accounts] [--developer-accounts] [--participant-accounts] [--chain-name] [--toml]"
+			cmd.Spec = "[--base][--full-accounts] [--validator-accounts] [--root-accounts] [--developer-accounts] " +
+				"[--participant-accounts] [--chain-name] [--toml]"
 
 			cmd.Action = func() {
 				specs := make([]spec.GenesisSpec, 0, *participantsOpt+*fullOpt)
+				for _, baseSpec := range *baseOpt {
+					genesisSpec := new(spec.GenesisSpec)
+					err := source.FromFile(baseSpec, genesisSpec)
+					if err != nil {
+						fatalf("could not read GenesisSpec: %v", err)
+					}
+					specs = append(specs, *genesisSpec)
+				}
 				for i := 0; i < *fullOpt; i++ {
 					specs = append(specs, spec.FullAccount(i))
 				}
@@ -106,7 +120,9 @@ func main() {
 					specs = append(specs, spec.ParticipantAccount(i))
 				}
 				genesisSpec := spec.MergeGenesisSpecs(specs...)
-				genesisSpec.ChainName = *chainNameOpt
+				if *chainNameOpt != "" {
+					genesisSpec.ChainName = *chainNameOpt
+				}
 				if *tomlOpt {
 					os.Stdout.WriteString(source.TOMLString(genesisSpec))
 				} else {
@@ -121,18 +137,24 @@ func main() {
 			genesisSpecOpt := cmd.StringOpt("s genesis-spec", "",
 				"A GenesisSpec to use as a template for a GenesisDoc that will be created along with keys")
 
-			tomlInOpt := cmd.BoolOpt("t toml-in", false, "Consume GenesisSpec/GenesisDoc as TOML "+
-				"rather than the JSON default")
+			jsonOutOpt := cmd.BoolOpt("j json-out", false, "Emit config in JSON rather than TOML "+
+				"suitable for further processing or forming a separate genesis.json GenesisDoc")
 
 			keysUrlOpt := cmd.StringOpt("k keys-url", "", fmt.Sprintf("Provide keys URL, default: %s",
 				keys.DefaultKeysConfig().URL))
 
-			jsonOutOpt := cmd.BoolOpt("j json-out", false, "Emit config in JSON rather than TOML "+
-				"suitable for further processing or forming a separate genesis.json GenesisDoc")
+			genesisDocOpt := cmd.StringOpt("g genesis-doc", "", "GenesisDoc in JSON or TOML to embed in config")
+
+			generateKeysOpt := cmd.StringOpt("x generate-keys", "",
+				"File to output containing secret keys as JSON or according to a custom template (see --keys-template). "+
+					"Note that using this options means the keys will not be generated in the default keys instance")
 
-			genesisDocOpt := cmd.StringOpt("g genesis-doc", "", "GenesisDoc JSON to embed in config")
+			keysTemplateOpt := cmd.StringOpt("z keys-template", mock.DefaultDumpKeysFormat,
+				fmt.Sprintf("Go text/template template (left delim: %s right delim: %s) to generate secret keys "+
+					"file specified with --generate-keys. Default:\n%s", mock.LeftTemplateDelim, mock.RightTemplateDelim,
+					mock.DefaultDumpKeysFormat))
 
-			separateGenesisDoc := cmd.StringOpt("s separate-genesis-doc", "", "Emit a separate genesis doc as JSON")
+			separateGenesisDoc := cmd.StringOpt("s separate-genesis-doc", "", "Emit a separate genesis doc as JSON or TOML")
 
 			validatorIndexOpt := cmd.IntOpt("v validator-index", -1,
 				"Validator index (in validators list - GenesisSpec or GenesisDoc) from which to set ValidatorAddress")
@@ -150,9 +172,9 @@ func main() {
 
 			chainNameOpt := cmd.StringOpt("n chain-name", "", "Default chain name")
 
-			cmd.Spec = "[--keys-url=<keys URL>] [--genesis-spec=<GenesisSpec file> | --genesis-doc=<GenesisDoc file>] " +
-				"[--separate-genesis-doc=<genesis JSON file>]" +
-				"[--validator-index=<index>] [--chain-name] [--toml-in] [--json-out] " +
+			cmd.Spec = "[--keys-url=<keys URL> | (--generate-keys=<secret keys files> [--keys-template=<text template for each key>])] " +
+				"[--genesis-spec=<GenesisSpec file> | --genesis-doc=<GenesisDoc file>] " +
+				"[--separate-genesis-doc=<genesis JSON file>] [--validator-index=<index>] [--chain-name] [--json-out] " +
 				"[--logging=<logging program>] [--describe-logging] [--debug]"
 
 			cmd.Action = func() {
@@ -160,7 +182,7 @@ func main() {
 
 				if *configOpt != "" {
 					// If explicitly given a config file use it as a base:
-					err := source.FromTOMLFile(*configOpt, conf)
+					err := source.FromFile(*configOpt, conf)
 					if err != nil {
 						fatalf("could not read base config file (as TOML): %v", err)
 					}
@@ -181,26 +203,41 @@ func main() {
 					conf.Keys.URL = *keysUrlOpt
 				}
 
+				// Genesis Spec
 				if *genesisSpecOpt != "" {
 					genesisSpec := new(spec.GenesisSpec)
-					err := fromFile(*genesisSpecOpt, *tomlInOpt, genesisSpec)
+					err := source.FromFile(*genesisSpecOpt, genesisSpec)
 					if err != nil {
 						fatalf("could not read GenesisSpec: %v", err)
 					}
-					keyClient := keys.NewKeyClient(conf.Keys.URL, logging.NewNoopLogger())
-					conf.GenesisDoc, err = genesisSpec.GenesisDoc(keyClient)
+					if *generateKeysOpt != "" {
+						keyClient := mock.NewMockKeyClient()
+						conf.GenesisDoc, err = genesisSpec.GenesisDoc(keyClient)
+
+						secretKeysString, err := keyClient.DumpKeys(*keysTemplateOpt)
+						if err != nil {
+							fatalf("Could not dump keys: %v", err)
+						}
+						err = ioutil.WriteFile(*generateKeysOpt, []byte(secretKeysString), 0700)
+						if err != nil {
+							fatalf("Could not write secret keys: %v", err)
+						}
+					} else {
+						conf.GenesisDoc, err = genesisSpec.GenesisDoc(keys.NewKeyClient(conf.Keys.URL, logging.NewNoopLogger()))
+					}
 					if err != nil {
 						fatalf("could not realise GenesisSpec: %v", err)
 					}
 				} else if *genesisDocOpt != "" {
 					genesisDoc := new(genesis.GenesisDoc)
-					err := fromFile(*genesisSpecOpt, *tomlInOpt, genesisDoc)
+					err := source.FromFile(*genesisSpecOpt, genesisDoc)
 					if err != nil {
 						fatalf("could not read GenesisSpec: %v", err)
 					}
 					conf.GenesisDoc = genesisDoc
 				}
 
+				// Which validator am I?
 				if *validatorIndexOpt > -1 {
 					if conf.GenesisDoc == nil {
 						fatalf("Unable to set ValidatorAddress from provided validator-index since no " +
@@ -216,6 +253,7 @@ func main() {
 					conf.ValidatorAddress = &conf.GenesisDoc.Validators[0].Address
 				}
 
+				// Logging
 				if *loggingOpt != "" {
 					ops := strings.Split(*loggingOpt, ",")
 					sinkConfig, err := presets.BuildSinkConfig(ops...)
@@ -288,10 +326,10 @@ func fatalf(format string, args ...interface{}) {
 func burrowConfigProvider(configFile string) source.ConfigProvider {
 	return source.FirstOf(
 		// Will fail if file doesn't exist, but still skipped it configFile == ""
-		source.TOMLFile(configFile, false),
+		source.File(configFile, false),
 		source.Environment(config.DefaultBurrowConfigJSONEnvironmentVariable),
 		// Try working directory
-		source.TOMLFile(config.DefaultBurrowConfigTOMLFileName, true),
+		source.File(config.DefaultBurrowConfigTOMLFileName, true),
 		source.Default(config.DefaultBurrowConfig()))
 }
 
@@ -308,7 +346,7 @@ func genesisDocProvider(genesisFile string, skipNonExistent bool) source.ConfigP
 					"in config cascade, only specify GenesisDoc in one place")
 			}
 			genesisDoc := new(genesis.GenesisDoc)
-			err := source.FromJSONFile(genesisFile, genesisDoc)
+			err := source.FromFile(genesisFile, genesisDoc)
 			if err != nil {
 				return err
 			}
@@ -316,18 +354,3 @@ func genesisDocProvider(genesisFile string, skipNonExistent bool) source.ConfigP
 			return nil
 		})
 }
-
-func fromFile(file string, toml bool, conf interface{}) (err error) {
-	if toml {
-		err = source.FromTOMLFile(file, conf)
-		if err != nil {
-			fatalf("could not read GenesisSpec: %v", err)
-		}
-	} else {
-		err = source.FromJSONFile(file, conf)
-		if err != nil {
-			fatalf("could not read GenesisSpec: %v", err)
-		}
-	}
-	return
-}
diff --git a/config/source/source.go b/config/source/source.go
index b4a31d809bb6d150eaaaa7af1b2c17522fc39d45..16a1ad7830a6dfd0944a68a73e53f016f9990ce3 100644
--- a/config/source/source.go
+++ b/config/source/source.go
@@ -13,6 +13,7 @@ import (
 	"github.com/BurntSushi/toml"
 	"github.com/cep21/xdgbasedir"
 	"github.com/imdario/mergo"
+	"regexp"
 )
 
 // If passed this identifier try to read config from STDIN
@@ -31,6 +32,16 @@ type ConfigProvider interface {
 
 var _ ConfigProvider = &configSource{}
 
+type Format string
+
+const (
+	JSON    Format = "JSON"
+	TOML    Format = "TOML"
+	Unknown Format = ""
+)
+
+var jsonRegex = regexp.MustCompile(`\s*{`)
+
 type configSource struct {
 	from  string
 	skip  bool
@@ -116,26 +127,14 @@ func EachOf(providers ...ConfigProvider) *configSource {
 	return Cascade(os.Stderr, false, providers...)
 }
 
-// Try to source config from provided JSON file, is skipNonExistent is true then the provider will fall-through (skip)
-// when the file doesn't exist, rather than returning an error
-func JSONFile(configFile string, skipNonExistent bool) *configSource {
+// Try to source config from provided file detecting the file format, is skipNonExistent is true then the provider will
+// fall-through (skip) when the file doesn't exist, rather than returning an error
+func File(configFile string, skipNonExistent bool) *configSource {
 	return &configSource{
 		skip: ShouldSkipFile(configFile, skipNonExistent),
 		from: fmt.Sprintf("JSON config file at '%s'", configFile),
 		apply: func(baseConfig interface{}) error {
-			return FromJSONFile(configFile, baseConfig)
-		},
-	}
-}
-
-// Try to source config from provided TOML file, is skipNonExistent is true then the provider will fall-through (skip)
-// when the file doesn't exist, rather than returning an error
-func TOMLFile(configFile string, skipNonExistent bool) *configSource {
-	return &configSource{
-		skip: ShouldSkipFile(configFile, skipNonExistent),
-		from: fmt.Sprintf("TOML config file at '%s'", configFile),
-		apply: func(baseConfig interface{}) error {
-			return FromTOMLFile(configFile, baseConfig)
+			return FromFile(configFile, baseConfig)
 		},
 	}
 }
@@ -157,7 +156,7 @@ func XDGBaseDir(configFileName string) *configSource {
 			if err != nil {
 				return err
 			}
-			return FromTOMLFile(configFile, baseConfig)
+			return FromFile(configFile, baseConfig)
 		},
 	}
 }
@@ -183,30 +182,39 @@ func Default(defaultConfig interface{}) *configSource {
 	}
 }
 
-func FromJSONFile(configFile string, conf interface{}) error {
+func FromFile(configFile string, conf interface{}) error {
 	bs, err := ReadFile(configFile)
 	if err != nil {
 		return err
 	}
 
-	return FromJSONString(string(bs), conf)
+	return FromString(string(bs), conf)
 }
 
-func FromTOMLFile(configFile string, conf interface{}) error {
-	bs, err := ReadFile(configFile)
+func FromTOMLString(tomlString string, conf interface{}) error {
+	_, err := toml.Decode(tomlString, conf)
 	if err != nil {
 		return err
 	}
+	return nil
+}
 
-	return FromTOMLString(string(bs), conf)
+func FromString(configString string, conf interface{}) error {
+	switch DetectFormat(configString) {
+	case JSON:
+		return FromJSONString(configString, conf)
+	case TOML:
+		return FromTOMLString(configString, conf)
+	default:
+		return fmt.Errorf("unknown configuration format:\n%s", configString)
+	}
 }
 
-func FromTOMLString(tomlString string, conf interface{}) error {
-	_, err := toml.Decode(tomlString, conf)
-	if err != nil {
-		return err
+func DetectFormat(configString string) Format {
+	if jsonRegex.MatchString(configString) {
+		return JSON
 	}
-	return nil
+	return TOML
 }
 
 func FromJSONString(jsonString string, conf interface{}) error {
diff --git a/config/source/source_test.go b/config/source/source_test.go
index 7bf062b40dd6e1713725eb74da0177ce1d2ea861..08cc5424bbae5398ba8211df50a75e2c926247ae 100644
--- a/config/source/source_test.go
+++ b/config/source/source_test.go
@@ -31,7 +31,7 @@ func TestFile(t *testing.T) {
 	file := writeConfigFile(t, newTestConfig())
 	defer os.Remove(file)
 	conf := new(animalConfig)
-	err := TOMLFile(file, false).Apply(conf)
+	err := File(file, false).Apply(conf)
 	assert.NoError(t, err)
 	assert.Equal(t, tomlString, TOMLString(conf))
 }
@@ -42,7 +42,7 @@ func TestCascade(t *testing.T) {
 	conf := newTestConfig()
 	err := Cascade(os.Stderr, true,
 		Environment(envVar),
-		TOMLFile("", false)).Apply(conf)
+		File("", false)).Apply(conf)
 	assert.NoError(t, err)
 	assert.Equal(t, newTestConfig(), conf)
 
@@ -53,7 +53,7 @@ func TestCascade(t *testing.T) {
 	conf = new(animalConfig)
 	err = Cascade(os.Stderr, true,
 		Environment(envVar),
-		TOMLFile(file, false)).Apply(conf)
+		File(file, false)).Apply(conf)
 	assert.NoError(t, err)
 	assert.Equal(t, TOMLString(fileConfig), TOMLString(conf))
 
@@ -66,11 +66,18 @@ func TestCascade(t *testing.T) {
 	conf = newTestConfig()
 	err = Cascade(os.Stderr, true,
 		Environment(envVar),
-		TOMLFile(file, false)).Apply(conf)
+		File(file, false)).Apply(conf)
 	assert.NoError(t, err)
 	assert.Equal(t, TOMLString(envConfig), TOMLString(conf))
 }
 
+func TestDetectFormat(t *testing.T) {
+	assert.Equal(t, TOML, DetectFormat(""))
+	assert.Equal(t, JSON, DetectFormat("{"))
+	assert.Equal(t, JSON, DetectFormat("\n\n\t    \n\n      {"))
+	assert.Equal(t, TOML, DetectFormat("[Tendermint]\n  Seeds =\"foobar@val0\"}"))
+}
+
 func writeConfigFile(t *testing.T, conf interface{}) string {
 	tomlString := TOMLString(conf)
 	f, err := ioutil.TempFile("", "source-test.toml")
diff --git a/keys/mock/key_client_mock.go b/keys/mock/key_client_mock.go
index 96a614cac83ebe8367a84e76fad91563a1198375..1faa65e269e5ed2fb4112f41c5adfd60e6ae8076 100644
--- a/keys/mock/key_client_mock.go
+++ b/keys/mock/key_client_mock.go
@@ -18,11 +18,18 @@ import (
 	"crypto/rand"
 	"fmt"
 
+	"bytes"
+	"text/template"
+
+	"encoding/base64"
+
 	acm "github.com/hyperledger/burrow/account"
 	. "github.com/hyperledger/burrow/keys"
 	"github.com/tendermint/ed25519"
 	crypto "github.com/tendermint/go-crypto"
+	"github.com/tmthrgd/go-hex"
 	"golang.org/x/crypto/ripemd160"
+	"github.com/pkg/errors"
 )
 
 //---------------------------------------------------------------------
@@ -30,14 +37,35 @@ import (
 
 // Simple ed25519 key structure for mock purposes with ripemd160 address
 type MockKey struct {
+	Name       string
 	Address    acm.Address
-	PrivateKey [ed25519.PrivateKeySize]byte
 	PublicKey  []byte
+	PrivateKey []byte
 }
 
-func newMockKey() (*MockKey, error) {
+const DefaultDumpKeysFormat = `{
+  "Keys": [<< range $index, $key := . >><< if $index>>,<< end >>
+    {
+      "Name": "<< $key.Name >>",
+      "Address": "<< $key.Address >>",
+      "PublicKey": "<< $key.PublicKeyBase64 >>",
+      "PrivateKey": "<< $key.PrivateKeyBase64 >>"
+    }<< end >>
+  ]
+}`
+
+const LeftTemplateDelim = "<<"
+const RightTemplateDelim = ">>"
+
+var DefaultDumpKeysTemplate = template.Must(template.New("MockKeyClient_DumpKeys").
+	Delims(LeftTemplateDelim, RightTemplateDelim).
+	Parse(DefaultDumpKeysFormat))
+
+func newMockKey(name string) (*MockKey, error) {
 	key := &MockKey{
-		PublicKey: make([]byte, ed25519.PublicKeySize),
+		Name:       name,
+		PublicKey:  make([]byte, ed25519.PublicKeySize),
+		PrivateKey: make([]byte, ed25519.PrivateKeySize),
 	}
 	// this is a mock key, so the entropy of the source is purely
 	// for testing
@@ -65,15 +93,34 @@ func mockKeyFromPrivateAccount(privateAccount acm.PrivateAccount) *MockKey {
 		panic(fmt.Errorf("mock key client only supports ed25519 private keys at present"))
 	}
 	key := &MockKey{
-		Address:   privateAccount.Address(),
-		PublicKey: privateAccount.PublicKey().RawBytes(),
+		Name:       privateAccount.Address().String(),
+		Address:    privateAccount.Address(),
+		PublicKey:  privateAccount.PublicKey().RawBytes(),
+		PrivateKey: privateAccount.PrivateKey().RawBytes(),
 	}
-	copy(key.PrivateKey[:], privateAccount.PrivateKey().RawBytes())
 	return key
 }
 
 func (mockKey *MockKey) Sign(message []byte) (acm.Signature, error) {
-	return acm.SignatureFromBytes(ed25519.Sign(&mockKey.PrivateKey, message)[:])
+	var privateKey [ed25519.PrivateKeySize]byte
+	copy(privateKey[:], mockKey.PrivateKey)
+	return acm.SignatureFromBytes(ed25519.Sign(&privateKey, message)[:])
+}
+
+func (mockKey *MockKey) PrivateKeyBase64() string {
+	return base64.StdEncoding.EncodeToString(mockKey.PrivateKey[:])
+}
+
+func (mockKey *MockKey) PrivateKeyHex() string {
+	return hex.EncodeUpperToString(mockKey.PrivateKey[:])
+}
+
+func (mockKey *MockKey) PublicKeyBase64() string {
+	return base64.StdEncoding.EncodeToString(mockKey.PublicKey)
+}
+
+func (mockKey *MockKey) PublicKeyHex() string {
+	return hex.EncodeUpperToString(mockKey.PublicKey)
 }
 
 //---------------------------------------------------------------------
@@ -96,26 +143,26 @@ func NewMockKeyClient(privateAccounts ...acm.PrivateAccount) *MockKeyClient {
 	return client
 }
 
-func (mock *MockKeyClient) NewKey() acm.Address {
+func (mkc *MockKeyClient) NewKey(name string) acm.Address {
 	// Only tests ED25519 curve and ripemd160.
-	key, err := newMockKey()
+	key, err := newMockKey(name)
 	if err != nil {
 		panic(fmt.Sprintf("Mocked key client failed on key generation: %s", err))
 	}
-	mock.knownKeys[key.Address] = key
+	mkc.knownKeys[key.Address] = key
 	return key.Address
 }
 
-func (mock *MockKeyClient) Sign(signAddress acm.Address, message []byte) (acm.Signature, error) {
-	key := mock.knownKeys[signAddress]
+func (mkc *MockKeyClient) Sign(signAddress acm.Address, message []byte) (acm.Signature, error) {
+	key := mkc.knownKeys[signAddress]
 	if key == nil {
 		return acm.Signature{}, fmt.Errorf("Unknown address (%s)", signAddress)
 	}
 	return key.Sign(message)
 }
 
-func (mock *MockKeyClient) PublicKey(address acm.Address) (acm.PublicKey, error) {
-	key := mock.knownKeys[address]
+func (mkc *MockKeyClient) PublicKey(address acm.Address) (acm.PublicKey, error) {
+	key := mkc.knownKeys[address]
 	if key == nil {
 		return acm.PublicKey{}, fmt.Errorf("Unknown address (%s)", address)
 	}
@@ -124,10 +171,27 @@ func (mock *MockKeyClient) PublicKey(address acm.Address) (acm.PublicKey, error)
 	return acm.PublicKeyFromGoCryptoPubKey(pubKeyEd25519.Wrap())
 }
 
-func (mock *MockKeyClient) Generate(keyName string, keyType KeyType) (acm.Address, error) {
-	return mock.NewKey(), nil
+func (mkc *MockKeyClient) Generate(keyName string, keyType KeyType) (acm.Address, error) {
+	return mkc.NewKey(keyName), nil
 }
 
-func (mock *MockKeyClient) HealthCheck() error {
+func (mkc *MockKeyClient) HealthCheck() error {
 	return nil
 }
+
+func (mkc *MockKeyClient) DumpKeys(templateString string) (string, error) {
+	tmpl, err := template.New("DumpKeys").Delims(LeftTemplateDelim, RightTemplateDelim).Parse(templateString)
+	if err != nil {
+		errors.Wrap(err, "could not dump keys to template")
+	}
+	buf := new(bytes.Buffer)
+	keys := make([]*MockKey, 0, len(mkc.knownKeys))
+	for _, k := range mkc.knownKeys {
+		keys = append(keys, k)
+	}
+	err = tmpl.Execute(buf, keys)
+	if err != nil {
+		return "", err
+	}
+	return buf.String(), nil
+}
diff --git a/keys/mock/key_client_mock_test.go b/keys/mock/key_client_mock_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..ffcec0019c000eecfa116ec1be3e2b275cb6c992
--- /dev/null
+++ b/keys/mock/key_client_mock_test.go
@@ -0,0 +1,29 @@
+package mock
+
+import (
+	"testing"
+
+	"encoding/json"
+
+	"github.com/hyperledger/burrow/keys"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestMockKeyClient_DumpKeys(t *testing.T) {
+	keyClient := NewMockKeyClient()
+	_, err := keyClient.Generate("foo", keys.KeyTypeEd25519Ripemd160)
+	require.NoError(t, err)
+	_, err = keyClient.Generate("foobar", keys.KeyTypeEd25519Ripemd160)
+	require.NoError(t, err)
+	dump, err := keyClient.DumpKeys(DefaultDumpKeysFormat)
+	require.NoError(t, err)
+
+	// Check JSON equal
+	var keys struct{ Keys []*MockKey }
+	err = json.Unmarshal([]byte(dump), &keys)
+	require.NoError(t, err)
+	bs, err := json.MarshalIndent(keys, "", "  ")
+	require.NoError(t, err)
+	assert.Equal(t, string(bs), dump)
+}