diff --git a/core/tests/test_recommendations.py b/core/tests/test_recommendations.py
new file mode 100644
index 0000000000000000000000000000000000000000..2aa62d1cc2b9a12c858e8b7bccaeda44f3e90f43
--- /dev/null
+++ b/core/tests/test_recommendations.py
@@ -0,0 +1,156 @@
+import requests_mock
+from rest_framework import status
+from rest_framework.test import APITestCase
+from django.contrib.auth.models import User
+
+from core.models.recommendations import Recommendation
+from core.models.accounts import Student
+from core.tests.mocks import mock_csui_oauth_verify, mock_csui_ldap_student, mock_csui_siak_student
+
+
+class RecommendationsTests(APITestCase):
+    def login(self, m):
+        mock_csui_oauth_verify(m)
+        mock_csui_ldap_student(m)
+        mock_csui_siak_student(m)
+        login_url = '/api/login/'
+        self.client.post(login_url, {'username': 'dummy.mahasiswa', 'password': 'lalala', 'login-type': 'sso-ui'},
+                         format='json')
+
+    @requests_mock.Mocker()
+    def test_feedback_list_with_more_than_10_recommendations_and_with_page_number(self, m):
+        self.login(m)
+
+        for _ in range(15):
+            Recommendation.objects.create(content="a recommendation")
+
+        recommendations_url = '/api/recommendations/'
+        recommendations_url_page_1 = '/api/recommendations/?page=1'
+        recommendations_urll_page_2 = '/api/recommendations/?page=2'
+
+        response1 = self.client.get(recommendations_url_page_1)
+        self.assertEqual(response1.status_code, status.HTTP_200_OK)
+        self.assertEqual(response1.data['count'], 15)
+        self.assertIn(recommendations_urll_page_2, response1.data['next'])
+        self.assertEqual(response1.data['previous'], None)
+        self.assertEqual(len(response1.data['results']), 10)
+
+        response2 = self.client.get(recommendations_urll_page_2)
+        self.assertEqual(response2.status_code, status.HTTP_200_OK)
+        self.assertEqual(response2.data['count'], 15)
+        self.assertIn(recommendations_url, response2.data['previous'])
+        self.assertEqual(response2.data['next'], None)
+        self.assertEqual(len(response2.data['results']), 5)
+
+    # @requests_mock.Mocker()
+    # def test_recommendations_create_with_content(self, m):
+    #     self.login(m)
+
+    #     user1 = User.objects.create_user(
+    #         'dummy.mahasiswa3', 'dummy.mahasiswa3@mahasiswa.com', 'lalala')
+    #     student1 = Student.objects.create(user=user1, npm="1212120010")
+    #     student1.save()
+
+    #     user2 = User.objects.create_user(
+    #         'dummy.mahasiswa2', 'dummy.mahasiswa2@mahasiswa.com', 'lalala123')
+    #     student2 = Student.objects.create(user=user2, npm="1314151518")
+    #     student2.save()
+
+    #     recommendations_url = '/api/recommendations/'
+    #     response = self.client.post(recommendations_url,
+    #                                 {"content": "a content",
+    #                                  "recommendation_giver": {"pk": student1.id},
+    #                                  "recommendation_receiver": {"pk": student2.id}})
+
+    #     self.assertEqual(response.status_code, status.HTTP_200_OK)
+    #     self.assertEqual(response.data["content"], "a content")
+
+    @requests_mock.Mocker()
+    def test_recommendations_create_without_content(self, m):
+        self.login(m)
+
+        recommendations_url = '/api/recommendations/'
+        response = self.client.post(recommendations_url,
+                                    {"content": "a content"})
+
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+    @requests_mock.Mocker()
+    def test_recommendations_delete_with_exist_data_and_with_data_id(self, m):
+        self.login(m)
+
+        new_recommendation = Recommendation(content="a content")
+        new_recommendation.save()
+        recommendation_id = new_recommendation.id
+
+        recommendation_url = '/api/recommendations/' + \
+            str(recommendation_id) + '/'
+        response = self.client.delete(recommendation_url)
+
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertEqual(
+            response.data["detail"], "Recommendation with id {} has been removed".format(recommendation_id))
+
+    @requests_mock.Mocker()
+    def test_recommendations_delete_without_exist_data(self, m):
+        self.login(m)
+
+        new_recommendation = Recommendation(content="a content")
+        new_recommendation.save()
+        recommendation_id = new_recommendation.id
+        new_recommendation.delete()
+
+        recommendation_url = '/api/recommendations/' + \
+            str(recommendation_id) + '/'
+        response = self.client.delete(recommendation_url)
+
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(
+            response.data["detail"], "Recommendation with id {} doesn't exist".format(recommendation_id))
+
+    @requests_mock.Mocker()
+    def test_recommendations_delete_without_data_id(self, m):
+        self.login(m)
+
+        # test_feedbacks_delete_without_data_id
+        recommendations_url = '/api/recommendations/'
+        response = self.client.delete(recommendations_url)
+
+        self.assertEqual(response.status_code,
+                         status.HTTP_405_METHOD_NOT_ALLOWED)
+
+    @requests_mock.Mocker()
+    def test_feedbacks_update_with_exist_data_and_with_data_id(self, m):
+        self.login(m)
+
+        new_recommendation = Recommendation(content="a content")
+        new_recommendation.save()
+        recommendation_id = new_recommendation.id
+
+        recommendation_url = '/api/recommendations/' + \
+            str(recommendation_id) + '/'
+        response = self.client.put(recommendation_url,
+                                   {"content": "updated content"})
+
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertEqual(response.data["content"], "updated content")
+        self.assertEqual(
+            response.data["detail"], "Recommendation with id {} has been updated".format(recommendation_id))
+
+    @requests_mock.Mocker()
+    def test_feedbacks_update_with_not_exist_data_and_with_data_id(self, m):
+        self.login(m)
+
+        new_recommendation = Recommendation(content="a content")
+        new_recommendation.save()
+        recommendation_id = new_recommendation.id
+        new_recommendation.delete()
+
+        recommendation_url = '/api/recommendations/' + \
+            str(recommendation_id) + '/'
+        response = self.client.put(recommendation_url,
+                                   {"content": "updated content"})
+
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(
+            response.data["detail"], "Recommendation with id {} doesn't exist".format(recommendation_id))
diff --git a/kape/urls.py b/kape/urls.py
index 557e79e2f8a15bdc2dc47eb8ec6a753afcd690b2..5377d791abaa3f4dbb9ea2541c81d82a3b1a3679 100755
--- a/kape/urls.py
+++ b/kape/urls.py
@@ -28,6 +28,7 @@ from core.views.vacancies import VacancyViewSet, BookmarkedVacancyByStudentViewS
     CompanyApplicationViewSet, CompanyVacanciesViewSet, ApplicationViewSet, VacancyMilestoneViewSet, \
     AcceptOfferByStudentViewSet
 from core.views.feedbacks import FeedbackViewSet
+from core.views.recommendations import RecommendationViewSet
 
 schema_view = get_swagger_view()
 router = routers.DefaultRouter()
@@ -40,10 +41,12 @@ router.register(r'register', CompanyRegisterViewSet)
 router.register(r'vacancies', VacancyViewSet)
 router.register(r'applications', ApplicationViewSet)
 router.register(r'feedbacks', FeedbackViewSet)
+router.register(r'recommendations', RecommendationViewSet)
 # router.register(r'students/(?P<student_id>\d+)/profile', StudentProfileViewSet)
 router.register(r'vacancies/(?P<vacancy_id>\d+)/milestones', VacancyMilestoneViewSet,
                 base_name='vacancy-milestones')
-router.register(r'acceptoffer/(?P<student_id>\d+)/vacancy', AcceptOfferByStudentViewSet)
+router.register(r'acceptoffer/(?P<student_id>\d+)/vacancy',
+                AcceptOfferByStudentViewSet)
 router.register(r'students/(?P<student_id>\d+)/bookmarked-vacancies', BookmarkedVacancyByStudentViewSet,
                 base_name='bookmarked-vacancy-list')
 router.register(r'students/(?P<student_id>\d+)/applied-vacancies', StudentApplicationViewSet,
diff --git a/package-lock.json b/package-lock.json
index b647ddf0dcc5bc7cb858ce87d51a4cef9042bbaa..b8b760a60a7077b79dd3f811e7b089262319713b 100755
--- a/package-lock.json
+++ b/package-lock.json
@@ -3261,7 +3261,7 @@
     "axios": {
       "version": "0.19.0",
       "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz",
-      "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==",
+      "integrity": "sha1-jgm/89kSLhM/e4EByPvdAO09Krg=",
       "requires": {
         "follow-redirects": "1.5.10",
         "is-buffer": "^2.0.2"
@@ -3278,7 +3278,7 @@
         "follow-redirects": {
           "version": "1.5.10",
           "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
-          "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
+          "integrity": "sha1-e3qfmuov3/NnhqlP9kPtB/T/Xio=",
           "requires": {
             "debug": "=3.1.0"
           }
@@ -3286,7 +3286,7 @@
         "is-buffer": {
           "version": "2.0.4",
           "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
-          "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A=="
+          "integrity": "sha1-PlcvI8hBGlz9lVfISeNmXgspBiM="
         }
       }
     },
@@ -3313,7 +3313,7 @@
     "babel-core": {
       "version": "6.26.3",
       "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz",
-      "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==",
+      "integrity": "sha1-suLwnjQtDwyI4vAuBneUEl51wgc=",
       "dev": true,
       "requires": {
         "babel-code-frame": "^6.26.0",
@@ -5599,7 +5599,7 @@
     "eslint-plugin-import": {
       "version": "2.18.2",
       "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz",
-      "integrity": "sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==",
+      "integrity": "sha1-AvEYC5Cwd7M9RHoXojJs60AKzrY=",
       "dev": true,
       "requires": {
         "array-includes": "^3.0.3",
@@ -5628,7 +5628,7 @@
         "resolve": {
           "version": "1.12.0",
           "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
-          "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
+          "integrity": "sha1-P8ZEo1yEpIVUYJ/ybsUrZvpXffY=",
           "dev": true,
           "requires": {
             "path-parse": "^1.0.6"
@@ -6284,7 +6284,7 @@
     "fetch-mock": {
       "version": "5.13.1",
       "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-5.13.1.tgz",
-      "integrity": "sha512-eWUo2KI4sRGnRu8tKELCBfasALM5BfvrCxdI7J02j3eUM9mf+uYzJkURA0PSn/29JVapVrYFm+z+9XijXu1PdA==",
+      "integrity": "sha1-lVeUp389ly8WRLms5loP39YPHfc=",
       "requires": {
         "glob-to-regexp": "^0.3.0",
         "node-fetch": "^1.3.3",
@@ -7956,7 +7956,7 @@
     "isparta": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/isparta/-/isparta-4.1.1.tgz",
-      "integrity": "sha512-kGwkNqmALQzdfGhgo5o8kOA88p14R3Lwg0nfQ/qzv4IhB4rXarT9maPMaYbo6cms4poWbeulrlFlURLUR6rDwQ==",
+      "integrity": "sha1-yS5JZylGkU7FQHyAEWDzN04LfLQ=",
       "dev": true,
       "requires": {
         "babel-core": "^6.1.4",
@@ -7973,7 +7973,7 @@
         "esprima": {
           "version": "4.0.1",
           "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
-          "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+          "integrity": "sha1-E7BM2z5sXRnfkatph6hpVhmwqnE=",
           "dev": true
         }
       }
@@ -8623,7 +8623,7 @@
     "karma-firefox-launcher": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-1.2.0.tgz",
-      "integrity": "sha512-j9Zp8M8+VLq1nI/5xZGfzeaEPtGQ/vk3G+Y8vpmFWLvKLNZ2TDjD6cu2dUu7lDbu1HXNgatsAX4jgCZTkR9qhQ==",
+      "integrity": "sha1-ZP4D3RAwD5dU1I+ev78x9slKIAw=",
       "dev": true,
       "requires": {
         "is-wsl": "^2.1.0"
@@ -8675,7 +8675,7 @@
         "chalk": {
           "version": "2.4.2",
           "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
-          "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+          "integrity": "sha1-zUJUFnelQzPPVBpJEIwUMrRMlCQ=",
           "dev": true,
           "requires": {
             "ansi-styles": "^3.2.1",
@@ -9727,7 +9727,7 @@
     "moment": {
       "version": "2.24.0",
       "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
-      "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
+      "integrity": "sha1-DQVdU/UFKqZTyfbraLtdEr9cK1s="
     },
     "move-concurrently": {
       "version": "1.0.1",
@@ -10845,7 +10845,7 @@
     "react-chartjs-2": {
       "version": "2.8.0",
       "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.8.0.tgz",
-      "integrity": "sha512-BPpC+qfnh37DkcXvxRwA1rdD9rX/0AQrwru4VZTLofCCuZBwRsc7PbfxjilvoB6YlHhorwZu40YDWEQkoz7xfQ==",
+      "integrity": "sha1-HCTekfs3VfjEMCZ13n1m/dozl1k=",
       "requires": {
         "lodash": "^4.17.4",
         "prop-types": "^15.5.8"
@@ -10854,7 +10854,7 @@
     "react-ckeditor-wrapper": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/react-ckeditor-wrapper/-/react-ckeditor-wrapper-1.1.2.tgz",
-      "integrity": "sha512-/COVfezpSKFQxC/OjFoZf1PyzxTvUxzndlpGjEcajzjRgKPzSFZiCoh/VqqwGaaHJROO9pePQ9JxmJy2YlzDAQ==",
+      "integrity": "sha1-cvDGgo4X6mNxXV6YNvOrovhIfPM=",
       "requires": {
         "babel-runtime": "6.x",
         "classnames": "2.x",
@@ -10896,7 +10896,7 @@
     "react-hot-loader": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-3.1.3.tgz",
-      "integrity": "sha512-d7nZf78irxoGN5PY4zd6CSgZiroOhvIWzRast3qwTn4sSnBwlt08kV8WMQ9mitmxEdlCTwZt+5ClrRSjxWguMQ==",
+      "integrity": "sha1-b5KHcyaVjHywE0tRJHRReGkSYII=",
       "dev": true,
       "requires": {
         "global": "^4.3.0",
@@ -10909,7 +10909,7 @@
         "source-map": {
           "version": "0.6.1",
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=",
           "dev": true
         }
       }
@@ -12109,7 +12109,7 @@
     "starwars": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/starwars/-/starwars-1.0.1.tgz",
-      "integrity": "sha512-d23qkhEuFNlHas4/w2J6ZF6qS7jqy8OK4N3gfQbtTk/5Lt1wKe+xv0cIweuNxwtwBGVXvmsLV2mdUOBKfj12cA=="
+      "integrity": "sha1-+OIWt4KUs/y/ytJbRPJxF543U9U="
     },
     "static-extend": {
       "version": "0.1.2",