diff --git a/ud/lookup.go b/ud/lookup.go
index f766b838ad754f4e1b4ec000bf99328ce5b90c01..c75c52e83f7e55c55207a32429736eba6564ecfd 100644
--- a/ud/lookup.go
+++ b/ud/lookup.go
@@ -89,7 +89,6 @@ func (m *Manager) Lookup(uid *id.ID, callback lookupCallback, timeout time.Durat
 
 	// Send the request
 	rounds, _, err := m.net.SendE2E(msg, params.GetDefaultE2E())
-
 	if err != nil {
 		return errors.WithMessage(err, "Failed to send the lookup request")
 	}
@@ -126,7 +125,7 @@ func (m *Manager) Lookup(uid *id.ID, callback lookupCallback, timeout time.Durat
 		case response := <-responseChan:
 			if response.Error != "" {
 				err = errors.Errorf("User Discovery returned an error on "+
-					"Lookup: %s", response.Error)
+					"lookup: %s", response.Error)
 			} else {
 				pubkey := m.grp.NewIntFromBytes(response.PubKey)
 				c = contact.Contact{
diff --git a/ud/lookup_test.go b/ud/lookup_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..6b18671f7c862f3796834270c80b65b9ac3d630b
--- /dev/null
+++ b/ud/lookup_test.go
@@ -0,0 +1,415 @@
+package ud
+
+import (
+	"github.com/golang/protobuf/proto"
+	"gitlab.com/elixxir/client/globals"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/comms/network"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/e2e"
+	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/crypto/csprng"
+	"gitlab.com/xx_network/crypto/large"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/ndf"
+	"math/rand"
+	"reflect"
+	"testing"
+	"time"
+)
+
+// Happy path.
+func TestManager_Lookup(t *testing.T) {
+	// Set up manager
+	m := Manager{
+		rng:              fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG),
+		grp:              cyclic.NewGroup(large.NewInt(107), large.NewInt(2)),
+		storage:          storage.InitTestingSession(t),
+		udID:             id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		inProgressLookup: map[uint64]chan *LookupResponse{},
+		net:              newTestNetworkManager(t),
+	}
+
+	// Generate callback function
+	callbackChan := make(chan struct {
+		c   contact.Contact
+		err error
+	})
+	callback := func(c contact.Contact, err error) {
+		callbackChan <- struct {
+			c   contact.Contact
+			err error
+		}{c: c, err: err}
+	}
+
+	// Trigger lookup response chan
+	go func() {
+		time.Sleep(1 * time.Millisecond)
+		m.inProgressLookup[0] <- &LookupResponse{
+			PubKey: []byte{5},
+			Error:  "",
+		}
+	}()
+
+	uid := id.NewIdFromUInt(rand.Uint64(), id.User, t)
+
+	// Run the lookup
+	err := m.Lookup(uid, callback, 10*time.Millisecond)
+	if err != nil {
+		t.Errorf("Lookup() returned an error: %+v", err)
+	}
+
+	// Generate expected Send message
+	payload, err := proto.Marshal(&LookupSend{
+		UserID: uid.Marshal(),
+		CommID: m.commID - 1,
+	})
+	if err != nil {
+		t.Fatalf("Failed to marshal LookupSend: %+v", err)
+	}
+	expectedMsg := message.Send{
+		Recipient:   m.udID,
+		Payload:     payload,
+		MessageType: message.UdLookup,
+	}
+
+	// Verify the message is correct
+	if !reflect.DeepEqual(expectedMsg, m.net.(*testNetworkManager).msg) {
+		t.Errorf("Failed to send correct message."+
+			"\n\texpected: %+v\n\treceived: %+v",
+			expectedMsg, m.net.(*testNetworkManager).msg)
+	}
+
+	// Verify the callback is called
+	select {
+	case cb := <-callbackChan:
+		if cb.err != nil {
+			t.Errorf("Callback returned an error: %+v", cb.err)
+		}
+
+		expectedContact := contact.Contact{
+			ID:       uid,
+			DhPubKey: m.grp.NewIntFromBytes([]byte{5}),
+		}
+		if !reflect.DeepEqual(expectedContact, cb.c) {
+			t.Errorf("Failed to get expected Contact."+
+				"\n\texpected: %v\n\treceived: %v", expectedContact, cb.c)
+		}
+	case <-time.After(100 * time.Millisecond):
+		t.Error("Callback not called.")
+	}
+
+	if _, exists := m.inProgressLookup[m.commID-1]; exists {
+		t.Error("Failed to delete LookupResponse from inProgressLookup.")
+	}
+}
+
+// Error path: the LookupResponse returns an error.
+func TestManager_Lookup_LookupResponseError(t *testing.T) {
+	// Set up manager
+	m := Manager{
+		rng:              fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG),
+		grp:              cyclic.NewGroup(large.NewInt(107), large.NewInt(2)),
+		storage:          storage.InitTestingSession(t),
+		udID:             id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		inProgressLookup: map[uint64]chan *LookupResponse{},
+		net:              newTestNetworkManager(t),
+	}
+
+	// Generate callback function
+	callbackChan := make(chan struct {
+		c   contact.Contact
+		err error
+	})
+	callback := func(c contact.Contact, err error) {
+		callbackChan <- struct {
+			c   contact.Contact
+			err error
+		}{c: c, err: err}
+	}
+
+	// Trigger lookup response chan
+	go func() {
+		time.Sleep(1 * time.Millisecond)
+		m.inProgressLookup[0] <- &LookupResponse{
+			PubKey: nil,
+			Error:  "Error",
+		}
+	}()
+
+	uid := id.NewIdFromUInt(rand.Uint64(), id.User, t)
+
+	// Run the lookup
+	err := m.Lookup(uid, callback, 10*time.Millisecond)
+	if err != nil {
+		t.Errorf("Lookup() returned an error: %+v", err)
+	}
+
+	// Verify the callback is called
+	select {
+	case cb := <-callbackChan:
+		if cb.err == nil {
+			t.Error("Callback did not return an expected error.")
+		}
+
+		if !reflect.DeepEqual(contact.Contact{}, cb.c) {
+			t.Errorf("Failed to get expected Contact."+
+				"\n\texpected: %v\n\treceived: %v", contact.Contact{}, cb.c)
+		}
+	case <-time.After(100 * time.Millisecond):
+		t.Error("Callback not called.")
+	}
+
+	if _, exists := m.inProgressLookup[m.commID-1]; exists {
+		t.Error("Failed to delete LookupResponse from inProgressLookup.")
+	}
+}
+
+// Error path: the round event chan times out.
+func TestManager_Lookup_EventChanTimeout(t *testing.T) {
+	// Set up manager
+	m := Manager{
+		rng:              fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG),
+		grp:              cyclic.NewGroup(large.NewInt(107), large.NewInt(2)),
+		storage:          storage.InitTestingSession(t),
+		udID:             id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		inProgressLookup: map[uint64]chan *LookupResponse{},
+		net:              newTestNetworkManager(t),
+	}
+
+	// Generate callback function
+	callbackChan := make(chan struct {
+		c   contact.Contact
+		err error
+	})
+	callback := func(c contact.Contact, err error) {
+		callbackChan <- struct {
+			c   contact.Contact
+			err error
+		}{c: c, err: err}
+	}
+
+	uid := id.NewIdFromUInt(rand.Uint64(), id.User, t)
+
+	// Run the lookup
+	err := m.Lookup(uid, callback, 10*time.Millisecond)
+	if err != nil {
+		t.Errorf("Lookup() returned an error: %+v", err)
+	}
+
+	// Verify the callback is called
+	select {
+	case cb := <-callbackChan:
+		if cb.err == nil {
+			t.Error("Callback did not return an expected error.")
+		}
+
+		if !reflect.DeepEqual(contact.Contact{}, cb.c) {
+			t.Errorf("Failed to get expected Contact."+
+				"\n\texpected: %v\n\treceived: %v", contact.Contact{}, cb.c)
+		}
+	case <-time.After(100 * time.Millisecond):
+		t.Error("Callback not called.")
+	}
+
+	if _, exists := m.inProgressLookup[m.commID-1]; exists {
+		t.Error("Failed to delete LookupResponse from inProgressLookup.")
+	}
+}
+
+// Happy path.
+func TestManager_lookupProcess(t *testing.T) {
+	m := Manager{
+		rng:              fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG),
+		grp:              cyclic.NewGroup(large.NewInt(107), large.NewInt(2)),
+		storage:          storage.InitTestingSession(t),
+		udID:             id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		inProgressLookup: map[uint64]chan *LookupResponse{},
+		net:              newTestNetworkManager(t),
+	}
+
+	c := make(chan message.Receive)
+	quitCh := make(chan struct{})
+
+	// Generate expected Send message
+	payload, err := proto.Marshal(&LookupSend{
+		UserID: id.NewIdFromUInt(rand.Uint64(), id.User, t).Marshal(),
+		CommID: m.commID,
+	})
+	if err != nil {
+		t.Fatalf("Failed to marshal LookupSend: %+v", err)
+	}
+
+	m.inProgressLookup[m.commID] = make(chan *LookupResponse, 1)
+
+	// Trigger response chan
+	go func() {
+		time.Sleep(1 * time.Millisecond)
+		c <- message.Receive{
+			Payload:    payload,
+			Encryption: message.E2E,
+		}
+		time.Sleep(1 * time.Millisecond)
+		quitCh <- struct{}{}
+	}()
+
+	m.lookupProcess(c, quitCh)
+
+	select {
+	case response := <-m.inProgressLookup[m.commID]:
+		expectedResponse := &LookupResponse{}
+		if err := proto.Unmarshal(payload, expectedResponse); err != nil {
+			t.Fatalf("Failed to unmarshal payload: %+v", err)
+		}
+
+		if !reflect.DeepEqual(expectedResponse, response) {
+			t.Errorf("Recieved unexpected response."+
+				"\n\texpected: %+v\n\trecieved: %+v", expectedResponse, response)
+		}
+	case <-time.After(100 * time.Millisecond):
+		t.Error("Response not sent.")
+	}
+}
+
+// Error path: dropped lookup response due to incorrect message.Receive.
+func TestManager_lookupProcess_NoLookupResponse(t *testing.T) {
+	m := Manager{
+		rng:              fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG),
+		grp:              cyclic.NewGroup(large.NewInt(107), large.NewInt(2)),
+		storage:          storage.InitTestingSession(t),
+		udID:             id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		inProgressLookup: map[uint64]chan *LookupResponse{},
+		net:              newTestNetworkManager(t),
+	}
+
+	c := make(chan message.Receive)
+	quitCh := make(chan struct{})
+
+	// Trigger response chan
+	go func() {
+		time.Sleep(1 * time.Millisecond)
+		c <- message.Receive{}
+		time.Sleep(1 * time.Millisecond)
+		quitCh <- struct{}{}
+	}()
+
+	m.lookupProcess(c, quitCh)
+
+	select {
+	case response := <-m.inProgressLookup[m.commID]:
+		t.Errorf("Received unexpected response: %+v", response)
+	case <-time.After(10 * time.Millisecond):
+		return
+	}
+}
+
+// testNetworkManager is a test implementation of NetworkManager interface.
+type testNetworkManager struct {
+	instance *network.Instance
+	msg      message.Send
+}
+
+func (t *testNetworkManager) SendE2E(m message.Send, _ params.E2E) ([]id.Round,
+	e2e.MessageID, error) {
+	rounds := []id.Round{
+		id.Round(0),
+		id.Round(1),
+		id.Round(2),
+	}
+
+	t.msg = m
+
+	return rounds, e2e.MessageID{}, nil
+}
+
+func (t *testNetworkManager) SendUnsafe(message.Send, params.Unsafe) ([]id.Round, error) {
+	return nil, nil
+}
+
+func (t *testNetworkManager) SendCMIX(format.Message, params.CMIX) (id.Round, error) {
+	return 0, nil
+}
+
+func (t *testNetworkManager) GetInstance() *network.Instance {
+	return t.instance
+}
+
+func (t *testNetworkManager) GetHealthTracker() interfaces.HealthTracker {
+	return nil
+}
+
+func (t *testNetworkManager) Follow() (stoppable.Stoppable, error) {
+	return nil, nil
+}
+
+func (t *testNetworkManager) CheckGarbledMessages() {}
+
+func newTestNetworkManager(i interface{}) interfaces.NetworkManager {
+	switch i.(type) {
+	case *testing.T, *testing.M, *testing.B:
+		break
+	default:
+		globals.Log.FATAL.Panicf("initTesting is restricted to testing only."+
+			"Got %T", i)
+	}
+
+	commsManager := connect.NewManagerTesting(i)
+	instanceComms := &connect.ProtoComms{
+		Manager: commsManager,
+	}
+
+	thisInstance, err := network.NewInstanceTesting(instanceComms, getNDF(), getNDF(), nil, nil, i)
+	if err != nil {
+		globals.Log.FATAL.Panicf("Failed to create new test Instance: %v", err)
+	}
+
+	thisManager := &testNetworkManager{instance: thisInstance}
+
+	return thisManager
+}
+
+func getNDF() *ndf.NetworkDefinition {
+	return &ndf.NetworkDefinition{
+		E2E: ndf.Group{
+			Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B" +
+				"7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE" +
+				"DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F" +
+				"8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041" +
+				"023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45" +
+				"3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209" +
+				"6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29" +
+				"A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E" +
+				"37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2" +
+				"78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696" +
+				"015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E" +
+				"6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873" +
+				"847AEF49F66E43873",
+			Generator: "2",
+		},
+		CMIX: ndf.Group{
+			Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48" +
+				"C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F" +
+				"FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5" +
+				"B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2" +
+				"35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41" +
+				"F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE" +
+				"92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15" +
+				"3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B",
+			Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613" +
+				"D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4" +
+				"6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472" +
+				"085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5" +
+				"AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA" +
+				"3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71" +
+				"BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0" +
+				"DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7",
+		},
+	}
+}