diff --git a/context/stoppable/cleanup.go b/context/stoppable/cleanup.go
index 6c84deba1a01afbb48ed98947fa0ecfb5cbdb1ee..a733d74bc175d09403d2a681c8108b528a31d9f2 100644
--- a/context/stoppable/cleanup.go
+++ b/context/stoppable/cleanup.go
@@ -7,10 +7,10 @@ import (
 	"time"
 )
 
-// Wraps any stoppable and runs a callback after to stop for cleanup behavior
-// the cleanup is run under the remainder of the timeout but will not be canceled
-// if the timeout runs out
-// the cleanup function does not run if the thread does not stop
+// Cleanup wraps any stoppable and runs a callback after to stop for cleanup
+// behavior. The cleanup is run under the remainder of the timeout but will not
+// be canceled if the timeout runs out. The cleanup function does not run if the
+// thread does not stop.
 type Cleanup struct {
 	stop Stoppable
 	// the clean function receives how long it has to run before the timeout,
@@ -20,7 +20,7 @@ type Cleanup struct {
 	once    sync.Once
 }
 
-// Creates a new cleanup from the passed stoppable and the function
+// NewCleanup creates a new Cleanup from the passed stoppable and function.
 func NewCleanup(stop Stoppable, clean func(duration time.Duration) error) *Cleanup {
 	return &Cleanup{
 		stop:    stop,
@@ -29,18 +29,19 @@ func NewCleanup(stop Stoppable, clean func(duration time.Duration) error) *Clean
 	}
 }
 
-// returns true if the thread is still running and its cleanup has completed
+// IsRunning returns true if the thread is still running and its cleanup has
+// completed.
 func (c *Cleanup) IsRunning() bool {
 	return atomic.LoadUint32(&c.running) == 1
 }
 
-// returns the name of the stoppable denoting it has cleanup
+// Name returns the name of the stoppable denoting it has cleanup.
 func (c *Cleanup) Name() string {
 	return c.stop.Name() + " with cleanup"
 }
 
-// stops the contained stoppable and runs the cleanup function after.
-// the cleanup function does not run if the thread does not stop
+// Close stops the contained stoppable and runs the cleanup function after. The
+// cleanup function does not run if the thread does not stop.
 func (c *Cleanup) Close(timeout time.Duration) error {
 	var err error
 
@@ -49,14 +50,14 @@ func (c *Cleanup) Close(timeout time.Duration) error {
 			defer atomic.StoreUint32(&c.running, 0)
 			start := time.Now()
 
-			//run the stopable
+			// Run the stoppable
 			if err := c.stop.Close(timeout); err != nil {
 				err = errors.WithMessagef(err, "Cleanup for %s not executed",
 					c.stop.Name())
 				return
 			}
 
-			//run the cleanup function with the remaining time as a timeout
+			// Run the cleanup function with the remaining time as a timeout
 			elapsed := time.Since(start)
 
 			complete := make(chan error, 1)
@@ -69,11 +70,11 @@ func (c *Cleanup) Close(timeout time.Duration) error {
 			select {
 			case err := <-complete:
 				if err != nil {
-					err = errors.WithMessagef(err, "Cleanup for %s "+
-						"failed", c.stop.Name())
+					err = errors.WithMessagef(err, "Cleanup for %s failed",
+						c.stop.Name())
 				}
 			case <-timer.C:
-				err = errors.Errorf("Clean up for %s timedout", c.stop.Name())
+				err = errors.Errorf("Clean up for %s timeout", c.stop.Name())
 			}
 		})
 
diff --git a/context/stoppable/cleanup_test.go b/context/stoppable/cleanup_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..3d808212a09754692d8c573d74034610f88bcb45
--- /dev/null
+++ b/context/stoppable/cleanup_test.go
@@ -0,0 +1,55 @@
+package stoppable
+
+import (
+	"testing"
+)
+
+// Tests happy path of NewCleanup().
+func TestNewCleanup(t *testing.T) {
+	single := NewSingle("test name")
+	cleanup := NewCleanup(single, single.Close)
+
+	if cleanup.stop != single || cleanup.running != 0 {
+		t.Errorf("NewCleanup() returned Single with incorrect values."+
+			"\n\texpected:  stop: %v  running: %d\n\treceived:  stop: %v  running: %d",
+			single, cleanup.stop, 0, cleanup.running)
+	}
+}
+
+// Tests happy path of Cleanup.IsRunning().
+func TestCleanup_IsRunning(t *testing.T) {
+	single := NewSingle("test name")
+	cleanup := NewCleanup(single, single.Close)
+
+	if cleanup.IsRunning() {
+		t.Errorf("IsRunning() returned false when it should be running.")
+	}
+
+	cleanup.running = 1
+	if !cleanup.IsRunning() {
+		t.Errorf("IsRunning() returned true when it should not be running.")
+	}
+}
+
+// Tests happy path of Cleanup.Name().
+func TestCleanup_Name(t *testing.T) {
+	name := "test name"
+	single := NewSingle(name)
+	cleanup := NewCleanup(single, single.Close)
+
+	if name+" with cleanup" != cleanup.Name() {
+		t.Errorf("Name() returned the incorrect string."+
+			"\n\texpected: %s\n\treceived: %s", name+" with cleanup", cleanup.Name())
+	}
+}
+
+// Tests happy path of Cleanup.Close().
+func TestCleanup_Close(t *testing.T) {
+	single := NewSingle("test name")
+	cleanup := NewCleanup(single, single.Close)
+
+	err := cleanup.Close(0)
+	if err != nil {
+		t.Errorf("Close() returned an error: %v", err)
+	}
+}
diff --git a/context/stoppable/multi.go b/context/stoppable/multi.go
index c9ee1ff61fc79a76ed9f268be0fe009a597a0e81..061be492dd4efd2d6c618d7f9772a18fb8cc7ab7 100644
--- a/context/stoppable/multi.go
+++ b/context/stoppable/multi.go
@@ -17,7 +17,7 @@ type Multi struct {
 	once       sync.Once
 }
 
-//returns a new multi stoppable
+// NewMulti returns a new multi stoppable.
 func NewMulti(name string) *Multi {
 	return &Multi{
 		name:    name,
@@ -25,20 +25,20 @@ func NewMulti(name string) *Multi {
 	}
 }
 
-// returns true if the thread is still running
+// IsRunning returns true if the thread is still running.
 func (m *Multi) IsRunning() bool {
 	return atomic.LoadUint32(&m.running) == 1
 }
 
-// adds the given stoppable to the list of stoppables
+// Add adds the given stoppable to the list of stoppables.
 func (m *Multi) Add(stoppable Stoppable) {
 	m.mux.Lock()
 	m.stoppables = append(m.stoppables, stoppable)
 	m.mux.Unlock()
 }
 
-// returns the name of the multi stoppable and the names of all stoppables it
-// contains
+// Name returns the name of the multi stoppable and the names of all stoppables
+// it contains.
 func (m *Multi) Name() string {
 	m.mux.RLock()
 	names := m.name + ": {"
@@ -54,8 +54,8 @@ func (m *Multi) Name() string {
 	return names
 }
 
-// closes all child stoppers. It does not return their errors and assumes they
-// print them to the log
+// Close closes all child stoppers. It does not return their errors and assumes
+// they print them to the log.
 func (m *Multi) Close(timeout time.Duration) error {
 	var err error
 	m.once.Do(
@@ -63,18 +63,17 @@ func (m *Multi) Close(timeout time.Duration) error {
 			atomic.StoreUint32(&m.running, 0)
 
 			numErrors := uint32(0)
-
 			wg := &sync.WaitGroup{}
 
 			m.mux.Lock()
 			for _, stoppable := range m.stoppables {
 				wg.Add(1)
-				go func() {
+				go func(stoppable Stoppable) {
 					if stoppable.Close(timeout) != nil {
 						atomic.AddUint32(&numErrors, 1)
 					}
 					wg.Done()
-				}()
+				}(stoppable)
 			}
 			m.mux.Unlock()
 
@@ -89,5 +88,4 @@ func (m *Multi) Close(timeout time.Duration) error {
 		})
 
 	return err
-
 }
diff --git a/context/stoppable/multi_test.go b/context/stoppable/multi_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..70f90e286da556ab736308f490edce3b8f620d3e
--- /dev/null
+++ b/context/stoppable/multi_test.go
@@ -0,0 +1,115 @@
+package stoppable
+
+import (
+	"reflect"
+	"testing"
+	"time"
+)
+
+// Tests happy path of NewMulti().
+func TestNewMulti(t *testing.T) {
+	name := "test name"
+	multi := NewMulti(name)
+
+	if multi.name != name || multi.running != 1 {
+		t.Errorf("NewMulti() returned Multi with incorrect values."+
+			"\n\texpected:  name: %s  running: %d\n\treceived:  name: %s  running: %d",
+			name, 1, multi.name, multi.running)
+	}
+}
+
+// Tests happy path of Multi.IsRunning().
+func TestMulti_IsRunning(t *testing.T) {
+	multi := NewMulti("name")
+
+	if !multi.IsRunning() {
+		t.Errorf("IsRunning() returned false when it should be running.")
+	}
+
+	multi.running = 0
+	if multi.IsRunning() {
+		t.Errorf("IsRunning() returned true when it should not be running.")
+	}
+}
+
+// Tests happy path of Multi.Add().
+func TestMulti_Add(t *testing.T) {
+	multi := NewMulti("multi name")
+	singles := []*Single{
+		NewSingle("single name 1"),
+		NewSingle("single name 2"),
+		NewSingle("single name 3"),
+	}
+
+	for _, single := range singles {
+		multi.Add(single)
+	}
+
+	for i, single := range singles {
+		if !reflect.DeepEqual(single, multi.stoppables[i]) {
+			t.Errorf("Add() did not add the correct Stoppables."+
+				"\n\texpected: %#v\n\treceived: %#v", single, multi.stoppables[i])
+		}
+	}
+}
+
+// Tests happy path of Multi.Name().
+func TestMulti_Name(t *testing.T) {
+	name := "test name"
+	multi := NewMulti(name)
+	singles := []*Single{
+		NewSingle("single name 1"),
+		NewSingle("single name 2"),
+		NewSingle("single name 3"),
+	}
+	expectedNames := []string{
+		name + ": {}",
+		name + ": {" + singles[0].name + "}",
+		name + ": {" + singles[0].name + ", " + singles[1].name + "}",
+		name + ": {" + singles[0].name + ", " + singles[1].name + ", " + singles[2].name + "}",
+	}
+
+	for i, single := range singles {
+		if expectedNames[i] != multi.Name() {
+			t.Errorf("Name() returned the incorrect string."+
+				"\n\texpected: %s\n\treceived: %s", expectedNames[0], multi.Name())
+		}
+		multi.Add(single)
+	}
+}
+
+// Tests happy path of Multi.Close().
+func TestMulti_Close(t *testing.T) {
+	// Create new Multi and add Singles to it
+	multi := NewMulti("name")
+	singles := []*Single{
+		NewSingle("single name 1"),
+		NewSingle("single name 2"),
+		NewSingle("single name 3"),
+	}
+	for _, single := range singles {
+		multi.Add(single)
+	}
+
+	go func() {
+		select {
+		case <-singles[0].quit:
+		}
+		select {
+		case <-singles[1].quit:
+		}
+		select {
+		case <-singles[2].quit:
+		}
+	}()
+
+	err := multi.Close(5 * time.Millisecond)
+	if err != nil {
+		t.Errorf("Close() returned an error: %v", err)
+	}
+
+	err = multi.Close(0)
+	if err != nil {
+		t.Errorf("Close() returned an error: %v", err)
+	}
+}
diff --git a/context/stoppable/single.go b/context/stoppable/single.go
index 47a2259b068f731a09d01efda4f1322a9cccc182..acd8da0077427215934dae20b68ff9346396492c 100644
--- a/context/stoppable/single.go
+++ b/context/stoppable/single.go
@@ -8,8 +8,8 @@ import (
 	"time"
 )
 
-// Single allows stopping a single goroutine using a channel
-// adheres to the stoppable interface
+// Single allows stopping a single goroutine using a channel.
+// It adheres to the stoppable interface.
 type Single struct {
 	name    string
 	quit    chan struct{}
@@ -17,7 +17,7 @@ type Single struct {
 	once    sync.Once
 }
 
-//returns a new single stoppable
+// NewSingle returns a new single stoppable.
 func NewSingle(name string) *Single {
 	return &Single{
 		name:    name,
@@ -26,22 +26,22 @@ func NewSingle(name string) *Single {
 	}
 }
 
-// returns true if the thread is still running
+// IsRunning returns true if the thread is still running.
 func (s *Single) IsRunning() bool {
 	return atomic.LoadUint32(&s.running) == 1
 }
 
-// returns the read only channel it will send the stop signal on
-func (s *Single) Quit() <-chan struct{} {
+// Quit returns the read only channel it will send the stop signal on.
+func (s *Single) Quit() chan<- struct{} {
 	return s.quit
 }
 
-// returns the name of the thread. This is designed to be
+// Name returns the name of the thread. This is designed to be
 func (s *Single) Name() string {
 	return s.name
 }
 
-// Close signals thread to time out and closes if it is still running.
+// Close signals the thread to time out and closes if it is still running.
 func (s *Single) Close(timeout time.Duration) error {
 	var err error
 	s.once.Do(func() {
diff --git a/context/stoppable/single_test.go b/context/stoppable/single_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2343379ca3a21efc384ac47b503c5896ee0ae862
--- /dev/null
+++ b/context/stoppable/single_test.go
@@ -0,0 +1,96 @@
+package stoppable
+
+import (
+	"testing"
+	"time"
+)
+
+// Tests happy path of NewSingle().
+func TestNewSingle(t *testing.T) {
+	name := "test name"
+	single := NewSingle(name)
+
+	if single.name != name || single.running != 1 {
+		t.Errorf("NewSingle() returned Single with incorrect values."+
+			"\n\texpected:  name: %s  running: %d\n\treceived:  name: %s  running: %d",
+			name, 1, single.name, single.running)
+	}
+}
+
+// Tests happy path of Single.IsRunning().
+func TestSingle_IsRunning(t *testing.T) {
+	single := NewSingle("name")
+
+	if !single.IsRunning() {
+		t.Errorf("IsRunning() returned false when it should be running.")
+	}
+
+	single.running = 0
+	if single.IsRunning() {
+		t.Errorf("IsRunning() returned true when it should not be running.")
+	}
+}
+
+// Tests happy path of Single.Quit().
+func TestSingle_Quit(t *testing.T) {
+	single := NewSingle("name")
+
+	go func() {
+		time.Sleep(150 * time.Nanosecond)
+		single.Quit() <- struct{}{}
+	}()
+
+	timer := time.NewTimer(2 * time.Millisecond)
+	select {
+	case <-timer.C:
+		t.Errorf("Quit signal not received.")
+	case <-single.quit:
+	}
+}
+
+// Tests happy path of Single.Name().
+func TestSingle_Name(t *testing.T) {
+	name := "test name"
+	single := NewSingle(name)
+
+	if name != single.Name() {
+		t.Errorf("Name() returned the incorrect string."+
+			"\n\texpected: %s\n\treceived: %s", name, single.Name())
+	}
+}
+
+// Test happy path of Single.Close().
+func TestSingle_Close(t *testing.T) {
+	single := NewSingle("name")
+
+	go func() {
+		time.Sleep(150 * time.Nanosecond)
+		select {
+		case <-single.quit:
+		}
+	}()
+
+	err := single.Close(5 * time.Millisecond)
+	if err != nil {
+		t.Errorf("Close() returned an error: %v", err)
+	}
+}
+
+// Tests that Single.Close() returns an error when the timeout is reached.
+func TestSingle_Close_Error(t *testing.T) {
+	single := NewSingle("name")
+	expectedErr := single.name + " failed to close"
+
+	go func() {
+		time.Sleep(3 * time.Millisecond)
+		select {
+		case <-single.quit:
+		}
+	}()
+
+	err := single.Close(2 * time.Millisecond)
+	if err == nil {
+		t.Errorf("Close() did not return the expected error."+
+			"\n\texpected: %v\n\treceived: %v", expectedErr, err)
+	}
+}
diff --git a/context/stoppable/stoppable.go b/context/stoppable/stoppable.go
index 6f76d7348ed4a8fddb35f893e2be413b0f4a502e..e4436d5a6f971ae0f648e0fe7e16c0472cd8a2ab 100644
--- a/context/stoppable/stoppable.go
+++ b/context/stoppable/stoppable.go
@@ -2,7 +2,7 @@ package stoppable
 
 import "time"
 
-// Interface for stopping a goroutine
+// Interface for stopping a goroutine.
 type Stoppable interface {
 	Close(timeout time.Duration) error
 	IsRunning() bool
diff --git a/storage/conversation/partner.go b/storage/conversation/partner.go
index 2d3de1b6a3dd7578f7280dbd0f2087c20d323691..3533f65fd6ce51382fb0ec77507da6b92583aa14 100644
--- a/storage/conversation/partner.go
+++ b/storage/conversation/partner.go
@@ -12,11 +12,13 @@ import (
 	"time"
 )
 
-const conversationKeyPrefix = "conversation"
-const currentConversationVersion = 0
-const maxTruncatedID = math.MaxUint32
-const bottomRegion = maxTruncatedID / 4
-const topRegion = bottomRegion * 3
+const (
+	conversationKeyPrefix      = "conversation"
+	currentConversationVersion = 0
+	maxTruncatedID             = math.MaxUint32
+	bottomRegion               = maxTruncatedID / 4
+	topRegion                  = bottomRegion * 3
+)
 
 type Conversation struct {
 	// Public & stored data
@@ -37,7 +39,8 @@ type conversationDisk struct {
 	NextSendID             uint64
 }
 
-// Returns the Conversation if it can be found, otherwise returns a new partner
+// LoadOrMakeConversation returns the Conversation if it can be found, otherwise
+// returns a new partner.
 func LoadOrMakeConversation(kv *versioned.KV, partner *id.ID) *Conversation {
 
 	c, err := loadConversation(kv, partner)
@@ -61,11 +64,12 @@ func LoadOrMakeConversation(kv *versioned.KV, partner *id.ID) *Conversation {
 	return c
 }
 
-// Finds the full 64 bit message ID and updates the internal last message ID if
-// the new ID is newer
+// ProcessReceivedMessageID finds the full 64-bit message ID and updates the
+// internal last message ID if the new ID is newer.
 func (c *Conversation) ProcessReceivedMessageID(mid uint32) uint64 {
 	c.mux.Lock()
 	defer c.mux.Unlock()
+
 	var high uint32
 	switch cmp(c.lastReceivedID, mid) {
 	case 1:
@@ -101,14 +105,14 @@ func cmp(a, b uint32) int {
 	return 0
 }
 
-//returns the next sendID in both full and truncated formats
+// GetNextSendID returns the next sendID in both full and truncated formats.
 func (c *Conversation) GetNextSendID() (uint64, uint32) {
 	c.mux.Lock()
 	old := c.nextSentID
 	c.nextSentID++
 	if err := c.save(); err != nil {
-		jww.FATAL.Panicf("Failed to save after incrementing the sendID: "+
-			"%s", err)
+		jww.FATAL.Panicf("Failed to save after incrementing the sendID: %s",
+			err)
 	}
 	c.mux.Unlock()
 	return old, uint32(old & 0x00000000FFFFFFFF)
diff --git a/storage/conversation/partner_test.go b/storage/conversation/partner_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..0c6091abc3aee7ff1801d7dc3c648879aa878607
--- /dev/null
+++ b/storage/conversation/partner_test.go
@@ -0,0 +1,207 @@
+package conversation
+
+import (
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"reflect"
+	"testing"
+)
+
+// Tests happy path of LoadOrMakeConversation() when making a new Conversation.
+func TestLoadOrMakeConversation_Make(t *testing.T) {
+	// Set up test values
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partner := id.NewIdFromString("partner ID", id.User, t)
+	expectedConv := &Conversation{
+		lastReceivedID:         0,
+		numReceivedRevolutions: 0,
+		nextSentID:             0,
+		partner:                partner,
+		kv:                     kv,
+	}
+
+	// Create new Conversation
+	conv := LoadOrMakeConversation(kv, partner)
+
+	// Check that the result matches the expected Conversation
+	if !reflect.DeepEqual(expectedConv, conv) {
+		t.Errorf("LoadOrMakeConversation() made unexpected Conversation."+
+			"\n\texpected: %+v\n\treceived: %+v", expectedConv, conv)
+	}
+}
+
+// Tests happy path of LoadOrMakeConversation() when loading a Conversation.
+func TestLoadOrMakeConversation_Load(t *testing.T) {
+	// Set up test values
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partner := id.NewIdFromString("partner ID", id.User, t)
+	expectedConv := LoadOrMakeConversation(kv, partner)
+
+	// Load Conversation
+	conv := LoadOrMakeConversation(kv, partner)
+
+	// Check that the result matches the expected Conversation
+	if !reflect.DeepEqual(expectedConv, conv) {
+		t.Errorf("LoadOrMakeConversation() made unexpected Conversation."+
+			"\n\texpected: %+v\n\treceived: %+v", expectedConv, conv)
+	}
+}
+
+// Tests case 1 of Conversation.ProcessReceivedMessageID().
+func TestConversation_ProcessReceivedMessageID_Case_1(t *testing.T) {
+	// Set up test values
+	mid := uint32(5)
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partner := id.NewIdFromString("partner ID", id.User, t)
+	expectedConv := LoadOrMakeConversation(kv, partner)
+	expectedConv.lastReceivedID = mid
+	expectedConv.numReceivedRevolutions = 1
+	conv := LoadOrMakeConversation(kv, partner)
+	conv.lastReceivedID = topRegion + 5
+	expectedResult := uint64(expectedConv.numReceivedRevolutions)<<32 | uint64(mid)
+
+	result := conv.ProcessReceivedMessageID(mid)
+	if result != expectedResult {
+		t.Errorf("ProcessReceivedMessageID() did not product the expected "+
+			"result.\n\texpected: %+v\n\trecieved: %+v",
+			expectedResult, result)
+	}
+	if !reflect.DeepEqual(expectedConv, conv) {
+		t.Errorf("ProcessReceivedMessageID() did not product the expected "+
+			"Conversation.\n\texpected: %+v\n\trecieved: %+v",
+			expectedConv, conv)
+	}
+}
+
+// Tests case 0 of Conversation.ProcessReceivedMessageID().
+func TestConversation_ProcessReceivedMessageID_Case_0(t *testing.T) {
+	// Set up test values
+	mid := uint32(5)
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partner := id.NewIdFromString("partner ID", id.User, t)
+	expectedConv := LoadOrMakeConversation(kv, partner)
+	expectedConv.lastReceivedID = mid
+	conv := LoadOrMakeConversation(kv, partner)
+	expectedResult := uint64(expectedConv.numReceivedRevolutions)<<32 | uint64(mid)
+
+	result := conv.ProcessReceivedMessageID(mid)
+	if result != expectedResult {
+		t.Errorf("ProcessReceivedMessageID() did not product the expected "+
+			"result.\n\texpected: %+v\n\trecieved: %+v",
+			expectedResult, result)
+	}
+	if !reflect.DeepEqual(expectedConv, conv) {
+		t.Errorf("ProcessReceivedMessageID() did not product the expected "+
+			"Conversation.\n\texpected: %+v\n\trecieved: %+v",
+			expectedConv, conv)
+	}
+}
+
+// Tests case -1 of Conversation.ProcessReceivedMessageID().
+func TestConversation_ProcessReceivedMessageID_Case_Neg1(t *testing.T) {
+	// Set up test values
+	mid := uint32(topRegion + 5)
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partner := id.NewIdFromString("partner ID", id.User, t)
+	expectedConv := LoadOrMakeConversation(kv, partner)
+	expectedConv.lastReceivedID = bottomRegion - 5
+	conv := LoadOrMakeConversation(kv, partner)
+	conv.lastReceivedID = bottomRegion - 5
+	expectedResult := uint64(expectedConv.numReceivedRevolutions-1)<<32 | uint64(mid)
+
+	result := conv.ProcessReceivedMessageID(mid)
+	if result != expectedResult {
+		t.Errorf("ProcessReceivedMessageID() did not product the expected "+
+			"result.\n\texpected: %+v\n\trecieved: %+v",
+			expectedResult, result)
+	}
+	if !reflect.DeepEqual(expectedConv, conv) {
+		t.Errorf("ProcessReceivedMessageID() did not product the expected "+
+			"Conversation.\n\texpected: %+v\n\trecieved: %+v",
+			expectedConv, conv)
+	}
+}
+
+// Tests happy path of Conversation.GetNextSendID().
+func TestConversation_GetNextSendID(t *testing.T) {
+	// Set up test values
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partner := id.NewIdFromString("partner ID", id.User, t)
+	conv := LoadOrMakeConversation(kv, partner)
+	conv.nextSentID = maxTruncatedID - 100
+
+	for i := uint64(maxTruncatedID - 100); i < maxTruncatedID+100; i++ {
+		fullID, truncID := conv.GetNextSendID()
+		if fullID != i {
+			t.Errorf("Returned incorrect full sendID."+
+				"\n\texpected: %d\n\treceived: %d", i, fullID)
+		}
+		if truncID != uint32(i) {
+			t.Errorf("Returned incorrect truncated sendID."+
+				"\n\texpected: %d\n\treceived: %d", uint32(i), truncID)
+		}
+	}
+}
+
+// Tests the happy path of save() and loadConversation().
+func TestConversation_save_load(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partner := id.NewIdFromString("partner ID", id.User, t)
+	expectedConv := makeRandomConv(kv, partner)
+	expectedErr := "loadConversation() produced an error: Failed to Load " +
+		"conversation: object not found"
+
+	err := expectedConv.save()
+	if err != nil {
+		t.Errorf("save() produced an error: %v", err)
+	}
+
+	testConv, err := loadConversation(kv, partner)
+	if err != nil {
+		t.Errorf("loadConversation() produced an error: %v", err)
+	}
+
+	if !reflect.DeepEqual(expectedConv, testConv) {
+		t.Errorf("saving and loading Conversation failed."+
+			"\n\texpected: %+v\n\treceived: %+v", expectedConv, testConv)
+	}
+
+	_, err = loadConversation(versioned.NewKV(make(ekv.Memstore)), partner)
+	if err == nil {
+		t.Errorf("loadConversation() failed to produce an error."+
+			"\n\texpected: %s\n\treceived: %v", expectedErr, nil)
+	}
+}
+
+// Tests the happy path of marshal() and unmarshal().
+func TestConversation_marshal_unmarshal(t *testing.T) {
+	expectedConv := makeRandomConv(versioned.NewKV(make(ekv.Memstore)),
+		id.NewIdFromString("partner ID", id.User, t))
+	testConv := LoadOrMakeConversation(expectedConv.kv, expectedConv.partner)
+
+	data, err := expectedConv.marshal()
+	if err != nil {
+		t.Errorf("marshal() returned an error: %v", err)
+	}
+
+	err = testConv.unmarshal(data)
+	if err != nil {
+		t.Errorf("unmarshal() returned an error: %v", err)
+	}
+
+	if !reflect.DeepEqual(expectedConv, testConv) {
+		t.Errorf("marshaling and unmarshaling Conversation failed."+
+			"\n\texpected: %+v\n\treceived: %+v", expectedConv, testConv)
+	}
+}
+
+func makeRandomConv(kv *versioned.KV, partner *id.ID) *Conversation {
+	c := LoadOrMakeConversation(kv, partner)
+	c.lastReceivedID = rand.Uint32()
+	c.numReceivedRevolutions = rand.Uint32()
+	c.nextSentID = rand.Uint64()
+
+	return c
+}