diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml
index 873eb612c7..0e2aa5fd0d 100644
--- a/models/fixtures/repository.yml
+++ b/models/fixtures/repository.yml
@@ -33,6 +33,7 @@
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
+  num_watches: 0
 
 -
   id: 4
diff --git a/models/repo.go b/models/repo.go
index 13ff6dd4f7..490c8a3274 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -2256,109 +2256,6 @@ func (repos MirrorRepositoryList) LoadAttributes() error {
 	return repos.loadAttributes(x)
 }
 
-//  __      __         __         .__
-// /  \    /  \_____ _/  |_  ____ |  |__
-// \   \/\/   /\__  \\   __\/ ___\|  |  \
-//  \        /  / __ \|  | \  \___|   Y  \
-//   \__/\  /  (____  /__|  \___  >___|  /
-//        \/        \/          \/     \/
-
-// Watch is connection request for receiving repository notification.
-type Watch struct {
-	ID     int64 `xorm:"pk autoincr"`
-	UserID int64 `xorm:"UNIQUE(watch)"`
-	RepoID int64 `xorm:"UNIQUE(watch)"`
-}
-
-func isWatching(e Engine, userID, repoID int64) bool {
-	has, _ := e.Get(&Watch{0, userID, repoID})
-	return has
-}
-
-// IsWatching checks if user has watched given repository.
-func IsWatching(userID, repoID int64) bool {
-	return isWatching(x, userID, repoID)
-}
-
-func watchRepo(e Engine, userID, repoID int64, watch bool) (err error) {
-	if watch {
-		if isWatching(e, userID, repoID) {
-			return nil
-		}
-		if _, err = e.Insert(&Watch{RepoID: repoID, UserID: userID}); err != nil {
-			return err
-		}
-		_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoID)
-	} else {
-		if !isWatching(e, userID, repoID) {
-			return nil
-		}
-		if _, err = e.Delete(&Watch{0, userID, repoID}); err != nil {
-			return err
-		}
-		_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repoID)
-	}
-	return err
-}
-
-// WatchRepo watch or unwatch repository.
-func WatchRepo(userID, repoID int64, watch bool) (err error) {
-	return watchRepo(x, userID, repoID, watch)
-}
-
-func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
-	watches := make([]*Watch, 0, 10)
-	return watches, e.Find(&watches, &Watch{RepoID: repoID})
-}
-
-// GetWatchers returns all watchers of given repository.
-func GetWatchers(repoID int64) ([]*Watch, error) {
-	return getWatchers(x, repoID)
-}
-
-// GetWatchers returns range of users watching given repository.
-func (repo *Repository) GetWatchers(page int) ([]*User, error) {
-	users := make([]*User, 0, ItemsPerPage)
-	sess := x.Where("watch.repo_id=?", repo.ID).
-		Join("LEFT", "watch", "`user`.id=`watch`.user_id")
-	if page > 0 {
-		sess = sess.Limit(ItemsPerPage, (page-1)*ItemsPerPage)
-	}
-	return users, sess.Find(&users)
-}
-
-func notifyWatchers(e Engine, act *Action) error {
-	// Add feeds for user self and all watchers.
-	watches, err := getWatchers(e, act.RepoID)
-	if err != nil {
-		return fmt.Errorf("get watchers: %v", err)
-	}
-
-	// Add feed for actioner.
-	act.UserID = act.ActUserID
-	if _, err = e.InsertOne(act); err != nil {
-		return fmt.Errorf("insert new actioner: %v", err)
-	}
-
-	for i := range watches {
-		if act.ActUserID == watches[i].UserID {
-			continue
-		}
-
-		act.ID = 0
-		act.UserID = watches[i].UserID
-		if _, err = e.InsertOne(act); err != nil {
-			return fmt.Errorf("insert new action: %v", err)
-		}
-	}
-	return nil
-}
-
-// NotifyWatchers creates batch of actions for every watcher.
-func NotifyWatchers(act *Action) error {
-	return notifyWatchers(x, act)
-}
-
 // ___________           __
 // \_   _____/__________|  | __
 //  |    __)/  _ \_  __ \  |/ /
diff --git a/models/repo_watch.go b/models/repo_watch.go
new file mode 100644
index 0000000000..c3baab9d2f
--- /dev/null
+++ b/models/repo_watch.go
@@ -0,0 +1,103 @@
+// 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
+
+import "fmt"
+
+// Watch is connection request for receiving repository notification.
+type Watch struct {
+	ID     int64 `xorm:"pk autoincr"`
+	UserID int64 `xorm:"UNIQUE(watch)"`
+	RepoID int64 `xorm:"UNIQUE(watch)"`
+}
+
+func isWatching(e Engine, userID, repoID int64) bool {
+	has, _ := e.Get(&Watch{UserID: userID, RepoID: repoID})
+	return has
+}
+
+// IsWatching checks if user has watched given repository.
+func IsWatching(userID, repoID int64) bool {
+	return isWatching(x, userID, repoID)
+}
+
+func watchRepo(e Engine, userID, repoID int64, watch bool) (err error) {
+	if watch {
+		if isWatching(e, userID, repoID) {
+			return nil
+		}
+		if _, err = e.Insert(&Watch{RepoID: repoID, UserID: userID}); err != nil {
+			return err
+		}
+		_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoID)
+	} else {
+		if !isWatching(e, userID, repoID) {
+			return nil
+		}
+		if _, err = e.Delete(&Watch{0, userID, repoID}); err != nil {
+			return err
+		}
+		_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repoID)
+	}
+	return err
+}
+
+// WatchRepo watch or unwatch repository.
+func WatchRepo(userID, repoID int64, watch bool) (err error) {
+	return watchRepo(x, userID, repoID, watch)
+}
+
+func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
+	watches := make([]*Watch, 0, 10)
+	return watches, e.Find(&watches, &Watch{RepoID: repoID})
+}
+
+// GetWatchers returns all watchers of given repository.
+func GetWatchers(repoID int64) ([]*Watch, error) {
+	return getWatchers(x, repoID)
+}
+
+// GetWatchers returns range of users watching given repository.
+func (repo *Repository) GetWatchers(page int) ([]*User, error) {
+	users := make([]*User, 0, ItemsPerPage)
+	sess := x.Where("watch.repo_id=?", repo.ID).
+		Join("LEFT", "watch", "`user`.id=`watch`.user_id")
+	if page > 0 {
+		sess = sess.Limit(ItemsPerPage, (page-1)*ItemsPerPage)
+	}
+	return users, sess.Find(&users)
+}
+
+func notifyWatchers(e Engine, act *Action) error {
+	// Add feeds for user self and all watchers.
+	watches, err := getWatchers(e, act.RepoID)
+	if err != nil {
+		return fmt.Errorf("get watchers: %v", err)
+	}
+
+	// Add feed for actioner.
+	act.UserID = act.ActUserID
+	if _, err = e.InsertOne(act); err != nil {
+		return fmt.Errorf("insert new actioner: %v", err)
+	}
+
+	for i := range watches {
+		if act.ActUserID == watches[i].UserID {
+			continue
+		}
+
+		act.ID = 0
+		act.UserID = watches[i].UserID
+		if _, err = e.InsertOne(act); err != nil {
+			return fmt.Errorf("insert new action: %v", err)
+		}
+	}
+	return nil
+}
+
+// NotifyWatchers creates batch of actions for every watcher.
+func NotifyWatchers(act *Action) error {
+	return notifyWatchers(x, act)
+}
diff --git a/models/repo_watch_test.go b/models/repo_watch_test.go
new file mode 100644
index 0000000000..a1543566c1
--- /dev/null
+++ b/models/repo_watch_test.go
@@ -0,0 +1,98 @@
+// 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
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestIsWatching(t *testing.T) {
+	assert.NoError(t, PrepareTestDatabase())
+
+	assert.True(t, IsWatching(1, 1))
+	assert.True(t, IsWatching(4, 1))
+
+	assert.False(t, IsWatching(1, 5))
+	assert.False(t, IsWatching(NonexistentID, NonexistentID))
+}
+
+func TestWatchRepo(t *testing.T) {
+	assert.NoError(t, PrepareTestDatabase())
+	const repoID = 3
+	const userID = 2
+
+	assert.NoError(t, WatchRepo(userID, repoID, true))
+	AssertExistsAndLoadBean(t, &Watch{RepoID: repoID, UserID: userID})
+	CheckConsistencyFor(t, &Repository{ID: repoID})
+
+	assert.NoError(t, WatchRepo(userID, repoID, false))
+	AssertNotExistsBean(t, &Watch{RepoID: repoID, UserID: userID})
+	CheckConsistencyFor(t, &Repository{ID: repoID})
+}
+
+func TestGetWatchers(t *testing.T) {
+	assert.NoError(t, PrepareTestDatabase())
+
+	repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
+	watches, err := GetWatchers(repo.ID)
+	assert.NoError(t, err)
+	assert.Len(t, watches, repo.NumWatches)
+	for _, watch := range watches {
+		assert.EqualValues(t, repo.ID, watch.RepoID)
+	}
+
+	watches, err = GetWatchers(NonexistentID)
+	assert.NoError(t, err)
+	assert.Len(t, watches, 0)
+}
+
+func TestRepository_GetWatchers(t *testing.T) {
+	assert.NoError(t, PrepareTestDatabase())
+
+	repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
+	watchers, err := repo.GetWatchers(1)
+	assert.NoError(t, err)
+	assert.Len(t, watchers, repo.NumWatches)
+	for _, watcher := range watchers {
+		AssertExistsAndLoadBean(t, &Watch{UserID: watcher.ID, RepoID: repo.ID})
+	}
+
+	repo = AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository)
+	watchers, err = repo.GetWatchers(1)
+	assert.NoError(t, err)
+	assert.Len(t, watchers, 0)
+}
+
+func TestNotifyWatchers(t *testing.T) {
+	assert.NoError(t, PrepareTestDatabase())
+
+	action := &Action{
+		ActUserID: 8,
+		RepoID:    1,
+		OpType:    ActionStarRepo,
+	}
+	assert.NoError(t, NotifyWatchers(action))
+
+	AssertExistsAndLoadBean(t, &Action{
+		ActUserID: action.ActUserID,
+		UserID:    1,
+		RepoID:    action.RepoID,
+		OpType:    action.OpType,
+	})
+	AssertExistsAndLoadBean(t, &Action{
+		ActUserID: action.ActUserID,
+		UserID:    4,
+		RepoID:    action.RepoID,
+		OpType:    action.OpType,
+	})
+	AssertExistsAndLoadBean(t, &Action{
+		ActUserID: action.ActUserID,
+		UserID:    8,
+		RepoID:    action.RepoID,
+		OpType:    action.OpType,
+	})
+}