diff --git a/models/fixtures/issue.yml b/models/fixtures/issue.yml
index 8bcadb0aae..662f2235d7 100644
--- a/models/fixtures/issue.yml
+++ b/models/fixtures/issue.yml
@@ -3,6 +3,7 @@
   repo_id: 1
   index: 1
   poster_id: 1
+  assignee_id: 1
   name: issue1
   content: content1
   is_closed: false
diff --git a/models/fixtures/issue_user.yml b/models/fixtures/issue_user.yml
new file mode 100644
index 0000000000..b3f98a71d3
--- /dev/null
+++ b/models/fixtures/issue_user.yml
@@ -0,0 +1,23 @@
+-
+  id: 1
+  uid: 1
+  issue_id: 1
+  is_read: true
+  is_assigned: true
+  is_mentioned: false
+
+-
+  id: 2
+  uid: 2
+  issue_id: 1
+  is_read: true
+  is_assigned: false
+  is_mentioned: false
+
+-
+  id: 3
+  uid: 4
+  issue_id: 1
+  is_read: false
+  is_assigned: false
+  is_mentioned: false
diff --git a/models/issue.go b/models/issue.go
index 7d7591837e..2f4c157920 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -1106,70 +1106,6 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) {
 	return issues, nil
 }
 
-// .___                             ____ ___
-// |   | ______ ________ __   ____ |    |   \______ ___________
-// |   |/  ___//  ___/  |  \_/ __ \|    |   /  ___// __ \_  __ \
-// |   |\___ \ \___ \|  |  /\  ___/|    |  /\___ \\  ___/|  | \/
-// |___/____  >____  >____/  \___  >______//____  >\___  >__|
-//          \/     \/            \/             \/     \/
-
-// IssueUser represents an issue-user relation.
-type IssueUser struct {
-	ID          int64 `xorm:"pk autoincr"`
-	UID         int64 `xorm:"INDEX"` // User ID.
-	IssueID     int64
-	IsRead      bool
-	IsAssigned  bool
-	IsMentioned bool
-}
-
-func newIssueUsers(e *xorm.Session, repo *Repository, issue *Issue) error {
-	assignees, err := repo.getAssignees(e)
-	if err != nil {
-		return fmt.Errorf("getAssignees: %v", err)
-	}
-
-	// Poster can be anyone, append later if not one of assignees.
-	isPosterAssignee := false
-
-	// Leave a seat for poster itself to append later, but if poster is one of assignee
-	// and just waste 1 unit is cheaper than re-allocate memory once.
-	issueUsers := make([]*IssueUser, 0, len(assignees)+1)
-	for _, assignee := range assignees {
-		issueUsers = append(issueUsers, &IssueUser{
-			IssueID:    issue.ID,
-			UID:        assignee.ID,
-			IsAssigned: assignee.ID == issue.AssigneeID,
-		})
-		isPosterAssignee = isPosterAssignee || assignee.ID == issue.PosterID
-	}
-	if !isPosterAssignee {
-		issueUsers = append(issueUsers, &IssueUser{
-			IssueID: issue.ID,
-			UID:     issue.PosterID,
-		})
-	}
-
-	if _, err = e.Insert(issueUsers); err != nil {
-		return err
-	}
-	return nil
-}
-
-// NewIssueUsers adds new issue-user relations for new issue of repository.
-func NewIssueUsers(repo *Repository, issue *Issue) (err error) {
-	sess := x.NewSession()
-	defer sessionRelease(sess)
-	if err = sess.Begin(); err != nil {
-		return err
-	}
-
-	if err = newIssueUsers(sess, repo, issue); err != nil {
-		return err
-	}
-
-	return sess.Commit()
-}
 
 // UpdateIssueMentions extracts mentioned people from content and
 // updates issue-user relations for them.
@@ -1400,67 +1336,6 @@ func UpdateIssue(issue *Issue) error {
 	return updateIssue(x, issue)
 }
 
-func updateIssueUserByAssignee(e *xorm.Session, issue *Issue) (err error) {
-	if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE issue_id = ?", false, issue.ID); err != nil {
-		return err
-	}
-
-	// Assignee ID equals to 0 means clear assignee.
-	if issue.AssigneeID > 0 {
-		if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE uid = ? AND issue_id = ?", true, issue.AssigneeID, issue.ID); err != nil {
-			return err
-		}
-	}
-
-	return updateIssue(e, issue)
-}
-
-// UpdateIssueUserByAssignee updates issue-user relation for assignee.
-func UpdateIssueUserByAssignee(issue *Issue) (err error) {
-	sess := x.NewSession()
-	defer sessionRelease(sess)
-	if err = sess.Begin(); err != nil {
-		return err
-	}
-
-	if err = updateIssueUserByAssignee(sess, issue); err != nil {
-		return err
-	}
-
-	return sess.Commit()
-}
-
-// UpdateIssueUserByRead updates issue-user relation for reading.
-func UpdateIssueUserByRead(uid, issueID int64) error {
-	_, err := x.Exec("UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?", true, uid, issueID)
-	return err
-}
-
-// UpdateIssueUsersByMentions updates issue-user pairs by mentioning.
-func UpdateIssueUsersByMentions(e Engine, issueID int64, uids []int64) error {
-	for _, uid := range uids {
-		iu := &IssueUser{
-			UID:     uid,
-			IssueID: issueID,
-		}
-		has, err := e.Get(iu)
-		if err != nil {
-			return err
-		}
-
-		iu.IsMentioned = true
-		if has {
-			_, err = e.Id(iu.ID).AllCols().Update(iu)
-		} else {
-			_, err = e.Insert(iu)
-		}
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
 //    _____  .__.__                   __
 //   /     \ |__|  |   ____   _______/  |_  ____   ____   ____
 //  /  \ /  \|  |  | _/ __ \ /  ___/\   __\/  _ \ /    \_/ __ \
diff --git a/models/issue_user.go b/models/issue_user.go
new file mode 100644
index 0000000000..cb11a8a076
--- /dev/null
+++ b/models/issue_user.go
@@ -0,0 +1,113 @@
+// Copyright 2017 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package models
+
+import (
+	"fmt"
+)
+
+// IssueUser represents an issue-user relation.
+type IssueUser struct {
+	ID          int64 `xorm:"pk autoincr"`
+	UID         int64 `xorm:"INDEX"` // User ID.
+	IssueID     int64
+	IsRead      bool
+	IsAssigned  bool
+	IsMentioned bool
+}
+
+func newIssueUsers(e Engine, repo *Repository, issue *Issue) error {
+	assignees, err := repo.getAssignees(e)
+	if err != nil {
+		return fmt.Errorf("getAssignees: %v", err)
+	}
+
+	// Poster can be anyone, append later if not one of assignees.
+	isPosterAssignee := false
+
+	// Leave a seat for poster itself to append later, but if poster is one of assignee
+	// and just waste 1 unit is cheaper than re-allocate memory once.
+	issueUsers := make([]*IssueUser, 0, len(assignees)+1)
+	for _, assignee := range assignees {
+		issueUsers = append(issueUsers, &IssueUser{
+			IssueID:    issue.ID,
+			UID:        assignee.ID,
+			IsAssigned: assignee.ID == issue.AssigneeID,
+		})
+		isPosterAssignee = isPosterAssignee || assignee.ID == issue.PosterID
+	}
+	if !isPosterAssignee {
+		issueUsers = append(issueUsers, &IssueUser{
+			IssueID: issue.ID,
+			UID:     issue.PosterID,
+		})
+	}
+
+	if _, err = e.Insert(issueUsers); err != nil {
+		return err
+	}
+	return nil
+}
+
+func updateIssueUserByAssignee(e Engine, issue *Issue) (err error) {
+	if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE issue_id = ?", false, issue.ID); err != nil {
+		return err
+	}
+
+	// Assignee ID equals to 0 means clear assignee.
+	if issue.AssigneeID > 0 {
+		if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE uid = ? AND issue_id = ?", true, issue.AssigneeID, issue.ID); err != nil {
+			return err
+		}
+	}
+
+	return updateIssue(e, issue)
+}
+
+// UpdateIssueUserByAssignee updates issue-user relation for assignee.
+func UpdateIssueUserByAssignee(issue *Issue) (err error) {
+	sess := x.NewSession()
+	defer sessionRelease(sess)
+	if err = sess.Begin(); err != nil {
+		return err
+	}
+
+	if err = updateIssueUserByAssignee(sess, issue); err != nil {
+		return err
+	}
+
+	return sess.Commit()
+}
+
+// UpdateIssueUserByRead updates issue-user relation for reading.
+func UpdateIssueUserByRead(uid, issueID int64) error {
+	_, err := x.Exec("UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?", true, uid, issueID)
+	return err
+}
+
+// UpdateIssueUsersByMentions updates issue-user pairs by mentioning.
+func UpdateIssueUsersByMentions(e Engine, issueID int64, uids []int64) error {
+	for _, uid := range uids {
+		iu := &IssueUser{
+			UID:     uid,
+			IssueID: issueID,
+		}
+		has, err := e.Get(iu)
+		if err != nil {
+			return err
+		}
+
+		iu.IsMentioned = true
+		if has {
+			_, err = e.Id(iu.ID).AllCols().Update(iu)
+		} else {
+			_, err = e.Insert(iu)
+		}
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/models/issue_user_test.go b/models/issue_user_test.go
new file mode 100644
index 0000000000..e3ebfa8fa2
--- /dev/null
+++ b/models/issue_user_test.go
@@ -0,0 +1,74 @@
+// Copyright 2017 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package models
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_newIssueUsers(t *testing.T) {
+	assert.NoError(t, PrepareTestDatabase())
+
+	repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
+	newIssue := &Issue{
+		RepoID:   repo.ID,
+		PosterID: 4,
+		Index:    5,
+		Title:    "newTestIssueTitle",
+		Content:  "newTestIssueContent",
+	}
+
+	// artificially insert new issue
+	AssertSuccessfulInsert(t, newIssue)
+
+	assert.NoError(t, newIssueUsers(x, repo, newIssue))
+
+	// issue_user table should now have entries for new issue
+	AssertExistsAndLoadBean(t, &IssueUser{IssueID: newIssue.ID, UID: newIssue.PosterID})
+	AssertExistsAndLoadBean(t, &IssueUser{IssueID: newIssue.ID, UID: repo.OwnerID})
+}
+
+func TestUpdateIssueUserByAssignee(t *testing.T) {
+	assert.NoError(t, PrepareTestDatabase())
+	issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
+
+	// artificially change assignee in issue_user table
+	AssertSuccessfulInsert(t, &IssueUser{IssueID: issue.ID, UID: 5, IsAssigned: true})
+	_, err := x.Cols("is_assigned").
+		Update(&IssueUser{IsAssigned: false}, &IssueUser{IssueID: issue.ID, UID: issue.AssigneeID})
+	assert.NoError(t, err)
+
+	assert.NoError(t, UpdateIssueUserByAssignee(issue))
+
+	// issue_user table should now be correct again
+	AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: issue.AssigneeID}, "is_assigned=1")
+	AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: 5}, "is_assigned=0")
+}
+
+func TestUpdateIssueUserByRead(t *testing.T) {
+	assert.NoError(t, PrepareTestDatabase())
+	issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
+
+	assert.NoError(t, UpdateIssueUserByRead(4, issue.ID))
+	AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1")
+
+	assert.NoError(t, UpdateIssueUserByRead(4, issue.ID))
+	AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1")
+
+	assert.NoError(t, UpdateIssueUserByRead(NonexistentID, NonexistentID))
+}
+
+func TestUpdateIssueUsersByMentions(t *testing.T) {
+	assert.NoError(t, PrepareTestDatabase())
+	issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
+
+	uids := []int64{2, 5}
+	assert.NoError(t, UpdateIssueUsersByMentions(x, issue.ID, uids))
+	for _, uid := range uids {
+		AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: uid}, "is_mentioned=1")
+	}
+}
diff --git a/models/setup_for_test.go b/models/setup_for_test.go
index fd5a180aec..651e36e822 100644
--- a/models/setup_for_test.go
+++ b/models/setup_for_test.go
@@ -51,6 +51,7 @@ func CreateTestEngine() error {
 	if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil {
 		return err
 	}
+
 	fixtures, err = testfixtures.NewFolder(x.DB().DB, &testfixtures.SQLite{}, "fixtures/")
 	return err
 }
@@ -82,7 +83,8 @@ func BeanExists(t *testing.T, bean interface{}, conditions ...interface{}) bool
 func AssertExistsAndLoadBean(t *testing.T, bean interface{}, conditions ...interface{}) interface{} {
 	exists, err := loadBeanIfExists(bean, conditions...)
 	assert.NoError(t, err)
-	assert.True(t, exists)
+	assert.True(t, exists,
+		"Expected to find %+v (with conditions %+v), but did not", bean, conditions)
 	return bean
 }
 
@@ -92,3 +94,15 @@ func AssertNotExistsBean(t *testing.T, bean interface{}, conditions ...interface
 	assert.NoError(t, err)
 	assert.False(t, exists)
 }
+
+// AssertSuccessfulInsert assert that beans is successfully inserted
+func AssertSuccessfulInsert(t *testing.T, beans ...interface{}) {
+	_, err := x.Insert(beans...)
+	assert.NoError(t, err)
+}
+
+// AssertSuccessfulUpdate assert that bean is successfully updated
+func AssertSuccessfulUpdate(t *testing.T, bean interface{}, conditions ...interface{}) {
+	_, err := x.Update(bean, conditions...)
+	assert.NoError(t, err)
+}