diff --git a/models/fixtures/follow.yml b/models/fixtures/follow.yml
new file mode 100644
index 000000000..53db1e88b
--- /dev/null
+++ b/models/fixtures/follow.yml
@@ -0,0 +1,4 @@
+-
+  id: 1
+  user_id: 4
+  follow_id: 2
diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml
index 5a2dd2f08..409747aa1 100644
--- a/models/fixtures/user.yml
+++ b/models/fixtures/user.yml
@@ -26,6 +26,7 @@
   avatar_email: user2@example.com
   num_repos: 2
   num_stars: 2
+  num_followers: 1
 
 -
   id: 3
@@ -56,6 +57,7 @@
   avatar: avatar4
   avatar_email: user4@example.com
   num_repos: 0
+  num_following: 1
 
 -
   id: 5
@@ -72,6 +74,7 @@
   num_repos: 1
   allow_create_organization: false
   is_active: true
+  num_following: 0
 
 -
   id: 6
diff --git a/models/user.go b/models/user.go
index 54a60acd8..a7e22659e 100644
--- a/models/user.go
+++ b/models/user.go
@@ -1292,78 +1292,6 @@ func SearchUserByName(opts *SearchUserOptions) (users []*User, _ int64, _ error)
 	return users, count, sess.Find(&users)
 }
 
-// ___________    .__  .__
-// \_   _____/___ |  | |  |   ______  _  __
-//  |    __)/  _ \|  | |  |  /  _ \ \/ \/ /
-//  |     \(  <_> )  |_|  |_(  <_> )     /
-//  \___  / \____/|____/____/\____/ \/\_/
-//      \/
-
-// Follow represents relations of user and his/her followers.
-type Follow struct {
-	ID       int64 `xorm:"pk autoincr"`
-	UserID   int64 `xorm:"UNIQUE(follow)"`
-	FollowID int64 `xorm:"UNIQUE(follow)"`
-}
-
-// IsFollowing returns true if user is following followID.
-func IsFollowing(userID, followID int64) bool {
-	has, _ := x.Get(&Follow{UserID: userID, FollowID: followID})
-	return has
-}
-
-// FollowUser marks someone be another's follower.
-func FollowUser(userID, followID int64) (err error) {
-	if userID == followID || IsFollowing(userID, followID) {
-		return nil
-	}
-
-	sess := x.NewSession()
-	defer sessionRelease(sess)
-	if err = sess.Begin(); err != nil {
-		return err
-	}
-
-	if _, err = sess.Insert(&Follow{UserID: userID, FollowID: followID}); err != nil {
-		return err
-	}
-
-	if _, err = sess.Exec("UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?", followID); err != nil {
-		return err
-	}
-
-	if _, err = sess.Exec("UPDATE `user` SET num_following = num_following + 1 WHERE id = ?", userID); err != nil {
-		return err
-	}
-	return sess.Commit()
-}
-
-// UnfollowUser unmarks someone as another's follower.
-func UnfollowUser(userID, followID int64) (err error) {
-	if userID == followID || !IsFollowing(userID, followID) {
-		return nil
-	}
-
-	sess := x.NewSession()
-	defer sessionRelease(sess)
-	if err = sess.Begin(); err != nil {
-		return err
-	}
-
-	if _, err = sess.Delete(&Follow{UserID: userID, FollowID: followID}); err != nil {
-		return err
-	}
-
-	if _, err = sess.Exec("UPDATE `user` SET num_followers = num_followers - 1 WHERE id = ?", followID); err != nil {
-		return err
-	}
-
-	if _, err = sess.Exec("UPDATE `user` SET num_following = num_following - 1 WHERE id = ?", userID); err != nil {
-		return err
-	}
-	return sess.Commit()
-}
-
 // GetStarredRepos returns the repos starred by a particular user
 func GetStarredRepos(userID int64, private bool) ([]*Repository, error) {
 	sess := x.Where("star.uid=?", userID).
diff --git a/models/user_follow.go b/models/user_follow.go
new file mode 100644
index 000000000..ac0cf0a8f
--- /dev/null
+++ b/models/user_follow.go
@@ -0,0 +1,71 @@
+// Copyright 2017 The Gitea 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
+
+// Follow represents relations of user and his/her followers.
+type Follow struct {
+	ID       int64 `xorm:"pk autoincr"`
+	UserID   int64 `xorm:"UNIQUE(follow)"`
+	FollowID int64 `xorm:"UNIQUE(follow)"`
+}
+
+// IsFollowing returns true if user is following followID.
+func IsFollowing(userID, followID int64) bool {
+	has, _ := x.Get(&Follow{UserID: userID, FollowID: followID})
+	return has
+}
+
+// FollowUser marks someone be another's follower.
+func FollowUser(userID, followID int64) (err error) {
+	if userID == followID || IsFollowing(userID, followID) {
+		return nil
+	}
+
+	sess := x.NewSession()
+	defer sessionRelease(sess)
+	if err = sess.Begin(); err != nil {
+		return err
+	}
+
+	if _, err = sess.Insert(&Follow{UserID: userID, FollowID: followID}); err != nil {
+		return err
+	}
+
+	if _, err = sess.Exec("UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?", followID); err != nil {
+		return err
+	}
+
+	if _, err = sess.Exec("UPDATE `user` SET num_following = num_following + 1 WHERE id = ?", userID); err != nil {
+		return err
+	}
+	return sess.Commit()
+}
+
+// UnfollowUser unmarks someone as another's follower.
+func UnfollowUser(userID, followID int64) (err error) {
+	if userID == followID || !IsFollowing(userID, followID) {
+		return nil
+	}
+
+	sess := x.NewSession()
+	defer sessionRelease(sess)
+	if err = sess.Begin(); err != nil {
+		return err
+	}
+
+	if _, err = sess.Delete(&Follow{UserID: userID, FollowID: followID}); err != nil {
+		return err
+	}
+
+	if _, err = sess.Exec("UPDATE `user` SET num_followers = num_followers - 1 WHERE id = ?", followID); err != nil {
+		return err
+	}
+
+	if _, err = sess.Exec("UPDATE `user` SET num_following = num_following - 1 WHERE id = ?", userID); err != nil {
+		return err
+	}
+	return sess.Commit()
+}
+
diff --git a/models/user_follow_test.go b/models/user_follow_test.go
new file mode 100644
index 000000000..59392dcb1
--- /dev/null
+++ b/models/user_follow_test.go
@@ -0,0 +1,45 @@
+package models
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestIsFollowing(t *testing.T) {
+	assert.NoError(t, PrepareTestDatabase())
+	assert.True(t, IsFollowing(4, 2))
+	assert.False(t, IsFollowing(2, 4))
+	assert.False(t, IsFollowing(5, NonexistentID))
+	assert.False(t, IsFollowing(NonexistentID, 5))
+	assert.False(t, IsFollowing(NonexistentID, NonexistentID))
+}
+
+func TestFollowUser(t *testing.T) {
+	assert.NoError(t, PrepareTestDatabase())
+
+	testSuccess := func(followerID, followedID int64) {
+		assert.NoError(t, FollowUser(followerID, followedID))
+		AssertExistsAndLoadBean(t, &Follow{UserID: followerID, FollowID: followedID})
+	}
+	testSuccess(4, 2)
+	testSuccess(5, 2)
+
+	assert.NoError(t, FollowUser(2, 2))
+
+	CheckConsistencyFor(t, &User{})
+}
+
+func TestUnfollowUser(t *testing.T) {
+	assert.NoError(t, PrepareTestDatabase())
+
+	testSuccess := func(followerID, followedID int64) {
+		assert.NoError(t, UnfollowUser(followerID, followedID))
+		AssertNotExistsBean(t, &Follow{UserID: followerID, FollowID: followedID})
+	}
+	testSuccess(4, 2)
+	testSuccess(5, 2)
+	testSuccess(2, 2)
+
+	CheckConsistencyFor(t, &User{})
+}