diff --git a/.travis.yml b/.travis.yml
index 6cf2b1c..9173e54 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,9 +1,10 @@
 sudo: required
 
 services:
-      - docker
+    - docker
 before_install:
     - docker pull golang:1.9.2
 
 script:
+    - make test
     - make docker
diff --git a/Makefile b/Makefile
index 5964110..e97e788 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,4 @@
+.PHONY: test
 
 linux:
 	CGO_ENABLED=0 GOOS=linux go build -a -ldflags "-s -w" -installsuffix cgo -o ./jaas
@@ -7,3 +8,6 @@ darwin:
 
 docker:
 	docker build -t alexellis2/jaas:latest .
+
+test:
+	go test ./...
diff --git a/cmd/run.go b/cmd/run.go
index e4d85c8..32ca64a 100644
--- a/cmd/run.go
+++ b/cmd/run.go
@@ -27,6 +27,10 @@ var (
 	verbose     bool
 )
 
+type clientInterface interface {
+	client.CommonAPIClient
+}
+
 func init() {
 	rootCmd.AddCommand(runCmd)
 
@@ -78,6 +82,7 @@ func runTask(taskRequest TaskRequest) error {
 		fmt.Printf("Connected to.. OK %s\n", taskRequest.Networks)
 		fmt.Printf("Constraints: %s\n", taskRequest.Constraints)
 		fmt.Printf("envVars: %s\n", taskRequest.EnvVars)
+		fmt.Printf("envFiles: %s\n", taskRequest.EnvFiles)
 		fmt.Printf("Secrets: %s\n", taskRequest.Secrets)
 	}
 
@@ -94,7 +99,6 @@ func runTask(taskRequest TaskRequest) error {
 	var err error
 	c, err = client.NewEnvClient()
 	if err != nil {
-
 		return fmt.Errorf("is the Docker Daemon running? Error: %s", err.Error())
 	}
 
@@ -116,13 +120,7 @@ func runTask(taskRequest TaskRequest) error {
 		}
 	}
 
-	spec := makeSpec(taskRequest.Image, taskRequest.EnvVars)
-	if len(taskRequest.Networks) > 0 {
-		nets := []swarm.NetworkAttachmentConfig{
-			swarm.NetworkAttachmentConfig{Target: taskRequest.Networks[0]},
-		}
-		spec.Networks = nets
-	}
+	spec := makeServiceSpec(taskRequest, c)
 
 	createOptions := types.ServiceCreateOptions{}
 
@@ -131,18 +129,52 @@ func runTask(taskRequest TaskRequest) error {
 		fmt.Println("Using RegistryAuth")
 	}
 
-	placement := &swarm.Placement{}
-	if len(taskRequest.Constraints) > 0 {
-		placement.Constraints = taskRequest.Constraints
-		spec.TaskTemplate.Placement = placement
+	createResponse, _ := c.ServiceCreate(context.Background(), spec, createOptions)
+	opts := types.ServiceInspectOptions{InsertDefaults: true}
+
+	service, _, _ := c.ServiceInspectWithRaw(context.Background(), createResponse.ID, opts)
+	fmt.Printf("Service created: %s (%s)\n", service.Spec.Name, createResponse.ID)
+
+	taskExitCode := pollTask(c, createResponse.ID, timeoutVal, taskRequest.ShowLogs, taskRequest.RemoveService)
+	os.Exit(taskExitCode)
+	return nil
+}
+
+func makeServiceSpec(tr TaskRequest, c clientInterface) swarm.ServiceSpec {
+	max := uint64(1)
+	spec := swarm.ServiceSpec{
+		TaskTemplate: swarm.TaskSpec{
+			RestartPolicy: &swarm.RestartPolicy{
+				MaxAttempts: &max,
+				Condition:   swarm.RestartPolicyConditionNone,
+			},
+			ContainerSpec: &swarm.ContainerSpec{
+				Image: tr.Image,
+				Env:   tr.EnvVars,
+			},
+		},
 	}
+	attachNetworks(&spec, tr.Networks)
+	readEnvFiles(&spec, tr.EnvFiles)
+	setConstraints(&spec, tr.Constraints)
+	setCommand(&spec, tr.Command)
+	attachMounts(&spec, tr.Mounts)
+	attachSecrets(&spec, tr.Secrets, c)
+	return spec
+}
 
-	if len(taskRequest.Command) > 0 {
-		spec.TaskTemplate.ContainerSpec.Command = strings.Split(taskRequest.Command, " ")
+func attachNetworks(spec *swarm.ServiceSpec, networks []string) {
+	nets := []swarm.NetworkAttachmentConfig{}
+	for _, n := range networks {
+		nets = append(nets, swarm.NetworkAttachmentConfig{Target: n})
 	}
 
-	if len(taskRequest.EnvFiles) > 0 {
-		for _, file := range taskRequest.EnvFiles {
+	spec.Networks = nets
+}
+
+func readEnvFiles(spec *swarm.ServiceSpec, files []string) {
+	if len(files) > 0 {
+		for _, file := range files {
 			envs, err := readEnvs(file)
 			if err != nil {
 				fmt.Fprintf(os.Stderr, "%s", err)
@@ -154,11 +186,26 @@ func runTask(taskRequest TaskRequest) error {
 			}
 		}
 	}
+}
 
+func setConstraints(spec *swarm.ServiceSpec, constraints []string) {
+	if len(constraints) > 0 {
+		placement := &swarm.Placement{Constraints: constraints}
+		spec.TaskTemplate.Placement = placement
+	}
+}
+
+func setCommand(spec *swarm.ServiceSpec, command string) {
+	if len(command) > 0 {
+		spec.TaskTemplate.ContainerSpec.Command = strings.Split(command, " ")
+	}
+}
+
+func attachMounts(spec *swarm.ServiceSpec, mounts []string) {
 	spec.TaskTemplate.ContainerSpec.Mounts = []mount.Mount{}
-	for _, bindMount := range taskRequest.Mounts {
+	for _, bindMount := range mounts {
 		parts := strings.Split(bindMount, "=")
-		if len(parts) < 2 || len(parts) > 2 {
+		if len(parts) != 2 {
 			fmt.Fprintf(os.Stderr, "Bind-mounts must be specified as: src=dest, i.e. --mount /home/alex/tmp/=/tmp/\n")
 			os.Exit(1)
 		}
@@ -172,11 +219,13 @@ func runTask(taskRequest TaskRequest) error {
 			spec.TaskTemplate.ContainerSpec.Mounts = append(spec.TaskTemplate.ContainerSpec.Mounts, mountVal)
 		}
 	}
+}
 
-	secretList, err := c.SecretList(context.Background(), types.SecretListOptions{})
+func attachSecrets(spec *swarm.ServiceSpec, secrets []string, c clientInterface) {
+	secretList, _ := c.SecretList(context.Background(), types.SecretListOptions{})
 
 	spec.TaskTemplate.ContainerSpec.Secrets = []*swarm.SecretReference{}
-	for _, serviceSecret := range taskRequest.Secrets {
+	for _, serviceSecret := range secrets {
 		var secretID string
 		for _, s := range secretList {
 			if serviceSecret == s.Spec.Annotations.Name {
@@ -202,34 +251,6 @@ func runTask(taskRequest TaskRequest) error {
 
 		spec.TaskTemplate.ContainerSpec.Secrets = append(spec.TaskTemplate.ContainerSpec.Secrets, &secretVal)
 	}
-
-	createResponse, _ := c.ServiceCreate(context.Background(), spec, createOptions)
-	opts := types.ServiceInspectOptions{InsertDefaults: true}
-
-	service, _, _ := c.ServiceInspectWithRaw(context.Background(), createResponse.ID, opts)
-	fmt.Printf("Service created: %s (%s)\n", service.Spec.Name, createResponse.ID)
-
-	taskExitCode := pollTask(c, createResponse.ID, timeoutVal, taskRequest.ShowLogs, taskRequest.RemoveService)
-	os.Exit(taskExitCode)
-	return nil
-}
-
-func makeSpec(image string, envVars []string) swarm.ServiceSpec {
-	max := uint64(1)
-
-	spec := swarm.ServiceSpec{
-		TaskTemplate: swarm.TaskSpec{
-			RestartPolicy: &swarm.RestartPolicy{
-				MaxAttempts: &max,
-				Condition:   swarm.RestartPolicyConditionNone,
-			},
-			ContainerSpec: &swarm.ContainerSpec{
-				Image: image,
-				Env:   envVars,
-			},
-		},
-	}
-	return spec
 }
 
 func readEnvs(file string) ([]string, error) {
diff --git a/cmd/run_test.go b/cmd/run_test.go
new file mode 100644
index 0000000..caa471a
--- /dev/null
+++ b/cmd/run_test.go
@@ -0,0 +1,139 @@
+// Copyright (c) Alex Ellis 2017-2018. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+package cmd
+
+import (
+	"context"
+	"io/ioutil"
+	"os"
+	"reflect"
+	"strconv"
+	"testing"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/mount"
+	"github.com/docker/docker/api/types/swarm"
+	"github.com/docker/docker/client"
+)
+
+var validRequest = TaskRequest{
+	Image:         "input-image",
+	Networks:      []string{"net1", "net2"},
+	Constraints:   []string{"node.id=2ivku8v2gvtg4", "engine.labels.operatingsystem==ubuntu 14.04"},
+	EnvVars:       []string{"ev1=val1", "ev2=val2"},
+	Mounts:        []string{"hostVol1=taskVol1", "hostVol2=taskVol2"},
+	Secrets:       []string{"secret1", "secret2"},
+	ShowLogs:      true,
+	Timeout:       "12",
+	RemoveService: true,
+	RegistryAuth:  "true",
+	Command:       "echo 'some output'",
+}
+
+type fakeClient struct {
+	client.CommonAPIClient
+	secrets []string
+}
+
+func (fk fakeClient) SecretList(ctx context.Context, sopt types.SecretListOptions) ([]swarm.Secret, error) {
+	slist := []swarm.Secret{}
+	for i, secret := range fk.secrets {
+		a := swarm.Annotations{Name: secret}
+		sspec := swarm.SecretSpec{Annotations: a}
+		s := swarm.Secret{
+			ID:   strconv.Itoa(i),
+			Meta: swarm.Meta{},
+			Spec: sspec,
+		}
+
+		slist = append(slist, s)
+	}
+	return slist, nil
+}
+
+func newClient(secrets []string) fakeClient {
+	return fakeClient{secrets: secrets}
+}
+
+func contains(el string, array []string) bool {
+	for _, e := range array {
+		if el == e {
+			return true
+		}
+	}
+	return false
+}
+
+func TestMakeServiceSpecValid(t *testing.T) {
+	c := newClient([]string{"secret1", "secret2", "secret3"})
+
+	f1, _ := ioutil.TempFile("", "jaas_env")
+	f1body := []byte("f1var1=val11\nf1var2=val12\n")
+	f1.Write(f1body)
+	f1.Sync()
+	f2, _ := ioutil.TempFile("", "jaas_env")
+	f2body := []byte("f2var1=val21\nf2var2=val22\n")
+	f2.Write(f2body)
+	f2.Sync()
+	defer os.Remove(f1.Name())
+	defer os.Remove(f2.Name())
+	validRequest.EnvFiles = []string{f1.Name(), f2.Name()}
+
+	spec := makeServiceSpec(validRequest, c)
+	if spec.TaskTemplate.ContainerSpec.Image != "input-image" {
+		t.Errorf("Container spec image should be %s, was %s", validRequest.Image, spec.TaskTemplate.ContainerSpec.Image)
+	}
+
+	// Test networks
+	networkTargets := []string{}
+	for _, n := range spec.Networks {
+		networkTargets = append(networkTargets, n.Target)
+	}
+	if !reflect.DeepEqual(networkTargets, []string{"net1", "net2"}) {
+		t.Errorf("Container spec networks should be %s, was %s", validRequest.Networks, networkTargets)
+	}
+
+	// Test env vars from input and env files
+	inputEnv := []string{"ev1=val1", "ev2=val2", "f1var1=val11", "f1var2=val12", "f2var1=val21", "f2var2=val22"}
+	for _, ev := range inputEnv {
+		if !contains(ev, spec.TaskTemplate.ContainerSpec.Env) {
+			t.Errorf("Container spec env should contain %s, but only has %s", ev, spec.TaskTemplate.ContainerSpec.Env)
+		}
+	}
+
+	// Test constraints
+	if !reflect.DeepEqual(spec.TaskTemplate.Placement.Constraints, []string{"node.id=2ivku8v2gvtg4", "engine.labels.operatingsystem==ubuntu 14.04"}) {
+		t.Errorf("Container spec constraints should be %s, was %s", validRequest.Constraints, spec.TaskTemplate.Placement.Constraints)
+	}
+
+	// Test mounts
+	expectedMounts := []mount.Mount{
+		{Source: "hostVol1", Target: "taskVol1"},
+		{Source: "hostVol2", Target: "taskVol2"},
+	}
+	if !reflect.DeepEqual(spec.TaskTemplate.ContainerSpec.Mounts, expectedMounts) {
+		t.Error("Container spec mounts should include:")
+		for _, m := range expectedMounts {
+			t.Errorf("{Source: %s, Target: %s}", m.Source, m.Target)
+		}
+		t.Error("But contained instead:")
+		for _, m := range spec.TaskTemplate.ContainerSpec.Mounts {
+			t.Errorf("{Source: %s, Target: %s}", m.Source, m.Target)
+		}
+	}
+
+	// Test secrets
+	secretNames := []string{}
+	for _, s := range spec.TaskTemplate.ContainerSpec.Secrets {
+		secretNames = append(secretNames, s.SecretName)
+	}
+	if !reflect.DeepEqual(secretNames, []string{"secret1", "secret2"}) {
+		t.Errorf("Container spec secrets should be %s, was %s", validRequest.Secrets, secretNames)
+	}
+
+	// Test command
+	if !reflect.DeepEqual(spec.TaskTemplate.ContainerSpec.Command, []string{"echo", "'some", "output'"}) {
+		t.Errorf("Container spec command should be %s, was %s", []string{"echo", "'some", "output'"}, spec.TaskTemplate.ContainerSpec.Command)
+	}
+}