CSC/ECE 517 Fall 2021 - E2133. Write tests for popup controller.rb
The goal of this project is to add unit testing for the PopupController to raise statement coverage above 60%. See the Test Outline section for specifics of our work.
Project Introduction
The PopupController is responsible for preparing data that will be displayed in popup views. To render the data, which mostly concerns assignments, the controller makes many accesses to database tables such as Response, ResponseMap, Question, Questionnaire, Answer, and Participant. The previous team implemented some of the test cases to improve the coverage. The previous work test coverage was 96%. However, their test cases followed integration testing methodologies which were not consistent with Unit testing. We will be implementing Unit testing while retaining the current coverage.
Team
Yi Qiu (mentor)
- Sayali Parab, snparab1704
- Rageeni Sah, ragesah
- Priya Jakhar, priyajakhar
Files Involved
popup_controller.rb
popup_controller_spec.rb
Running Tests
What we need to do is to set up the environment and complete the 'popup_controller_spec.rb' to finish the unit tests for popup controller. This rspec test file can be run by calling the following code:
rspec spec/controllers/popup_controller_spec.rb
What needs to be done
1. Write RSpec unit test cases to make the statement coverage above 90%.
2. Cover as many boundary cases and the core functionality for all popup controller actions.
Test Plan
In total, we wrote tests to cover all 6 methods in the popup controller. We created unit test cases with the help of mocked objects (factory objects and stub objects).
Popup Controller Methods
The code of the controller can be found here. The methods are:
- action_allowed?
- author_feedback_popup
- team_users_popup
- view_review_scores_popup
- reviewer_details_popup
- self_review_popup
Mock Models
Test Frame
In case of controllers, unit testing is about testing the functional logic.
1. action_allowed? - This is the first action that takes place when user tries to access the popup. This checks whether the current user is authorized to view the pop up. The action is allowed for all users except the student.
Code snippet:
describe '#action_allowed?' do context 'when user does not have right privilege, it denies action' do it 'for no user' do; expect(controller.send(:action_allowed?)).to be false; end it 'for student' do stub_current_user(student, student.role.name, student.role) expect(controller.send(:action_allowed?)).to be false end end context 'when role has right privilege, it allows action' do it 'for Super Admin' do stub_current_user(superadmin, superadmin.role.name, superadmin.role) expect(controller.send(:action_allowed?)).to be true end it 'for Admin' do stub_current_user(admin, admin.role.name, admin.role) expect(controller.send(:action_allowed?)).to be true end it 'for Instructor' do stub_current_user(instructor, instructor.role.name, instructor.role) expect(controller.send(:action_allowed?)).to be true end it 'for TA' do stub_current_user(ta, ta.role.name, ta.role) expect(controller.send(:action_allowed?)).to be true end end end
2. author_feedback_popup - This action can be called from response_report by clicking on Student names from Instructor's end.
Code snippet:
describe '#author_feedback_popup' do context 'when response_id does not exist' do it 'fail to get any info' do; expect(controller.send(:author_feedback_popup)).to be nil; end end context 'when response_id exists' do it 'passes tests in first block' do allow(Question).to receive_message_chain(:find, :questionnaire_id).with(answer.question_id).with(no_args).and_return(questionnaire.id) allow(Questionnaire).to receive(:find).with(questionnaire.id).and_return(questionnaire) allow(questionnaire).to receive(:max_question_score).and_return(20) allow(Answer).to receive(:where).with(any_args).and_return([answer]) allow(Response).to receive(:find).with(any_args).and_return(response) allow(response).to receive(:average_score).and_return(16) allow(response).to receive(:aggregate_questionnaire_score).and_return(20) allow(response).to receive(:maximum_score).and_return(19) allow(Participant).to receive(:find).with("1").and_return(participant) allow(User).to receive(:find).with(participant.user_id).and_return(participant) params = {response_id: 1, reviewee_id: 1} session = {user: ta} result = get :author_feedback_popup, params, session expect(result.status).to eq 200 expect(controller.instance_variable_get(:@sum)).to eq 20 expect(controller.instance_variable_get(:@total_possible)).to eq 19 expect(controller.instance_variable_get(:@total_percentage)).to eq 16 end end end
3. team_users_popup - This action can be called from _review_report by clicking the team reviewed link from the instructor's end. This action is responsible to fetch team details that have reviewed the assignment. details: team name, team users, max score round, total possible rounds, comments, etc.
Findings: team_users_popup.html
Params: id, id2
Responses: sum team assignment team_users reviewer_id similar_assignments
Used variables in HTML view: team, team_users, ip, assignment, reviewer_id, response_round_ , response_id_round_ , scores_round_ , Max_score_round_ , Total_percentage_round_ , sum_round_ , total_possible_round_
Code snippet:
describe '#team_users_popup' do ## INSERT CONTEXT/DESCRIPTION/CODE HERE it "renders the page successfuly as Instructor" do allow(Team).to receive(:find).and_return(team) allow(Assignment).to receive(:find).and_return(assignment) reviewer = double(:reviewer1, reviewer_id: 1) user_id = double(:user_id, user_id: 1) response = double('response1', response_id: 1, responses: "this is test response") answer = double('answer', question_id: 1) allow(ResponseMap).to receive(:find).with(1).and_return(reviewer) allow(Participant).to receive(:find).with(1).and_return(user_id) allow(Response).to receive_message_chain(:where, :last).with(map_id: 1, round: 1).with(no_args).and_return(response) allow(Answer).to receive(:where).with(response_id: 1).and_return(answer) allow(Response).to receive_message_chain(:average_score).with(no_args).and_return(response) allow(Response).to receive_message_chain(:aggregate_questionnaire_score).with(no_args).and_return(response) allow(Response).to receive_message_chain(:maximum_score).with(no_args).and_return(response) params = {id: team.id} session = {user: instructor} result = get :team_users_popup, params, session expect(result.status).to eq 200 end end
4. view_review_scores_popup- This action can be called from _review_report by clicking the summary link from the instructor's end. This action is responsible to fetch the details of the scores that have been received for the assignment which includes further details such as the course, assignment, questions, responses, the team responsible for the review, review summary, etc.
Findings:
View_review_scores_popup.html
Params: reviewer_id, Assignment_id
Response: review_final_versions,
Used variables in HTML view: review_final_versions, review_final_versions.questionnaire_id, review_final_versions.response_ids
Test cases: If Reviewer_id is null, If Assignment_id is null, Assignment.vary_by_topic true - Provide Flash error, Assignment.vary_by_topic false - Does not provide Flash error, review_final_versions - Has the structure as expected by the rendering view page
Code snippet:
describe '#view_review_scores_popup'do context 'review tone analysis operation is performed' do it 'Prepares scores and review analysis report for rendering purpose' do allow(Assignment).to receive(:find).and_return(assignment) allow(Participant).to receive(:find).and_return(participant) params = {reviewer_id: participant.id, assignment_id: assignment.id} session = {user: instructor} result = get :view_review_scores_popup, params, session expect(controller.instance_variable_get(:@review_final_versions)).to eq final_versions end end end
5. reviewer_details_popup - This action can be called from response_report by clicking reviewer names from the instructor's end. This action is responsible to fetch reviewer details: full name, user id, email, and handle.
Code snippet:
describe '#reviewer_details_popup' do render_views it "render reviewer_details_popup page successfully" do participant = double(:participant, user_id: 1) user = double(:user, fullname: "Test User", name: "Test", email: "test@gmail.com", handle: 1) allow(Participant).to receive(:find).with("1").and_return(participant) allow(User).to receive(:find).with(participant.user_id).and_return(user) params = {id: 1, assignment_id: 1} session = {user: instructor} get :reviewer_details_popup, params, session expect(@response).to have_http_status(200) expect(user.fullname).to eq("Test User") expect(user.name).to eq("Test") expect(user.email).to eq("test@gmail.com") expect(user.handle).to eq(1) end end
6. self_review_popup - This action can be called from "response_report" by clicking reviewer names from the instructor's end. In this action for the given response id maximum score is displayed. If the response id is nil maximum score is set to 5, i.e., if a question response is not reviewed then the system automatically assigns five as the maximum score for the response.
Code snippet:
describe '#self_review_popup' do context "when current user is the participant" do it "render page successfully as Instructor to get maximum_score " do question1 = double(:question1, questionnaire_id: 1) questionnaire1 = double(:questionnaire1, min_question_score: 1, max_question_score: 3, name: 'Test questionnaire') answer1 = double('Answer', question_id: 1) response1 = double('response1', response_id: 1, responses: "this is test response") allow(Answer).to receive_message_chain(:where, :first).with(response_id: 1).with(no_args).and_return(answer1) allow(Question).to receive(:find).with(1).and_return(question1) allow(Questionnaire).to receive(:find).and_return(questionnaire1) allow(Answer).to receive(:where).with("1").and_return(answer1) allow(Response).to receive_message_chain(:find, :average_score).with("1").with(no_args).and_return(response1) allow(Response).to receive_message_chain(:find, :aggregate_questionnaire_score).with("1").with(no_args).and_return(response1) allow(Response).to receive_message_chain(:find, :maximum_score).with("1").with(no_args).and_return(response1) params = {response_id: 1, user_fullname: questionnaire1.name} session = {user: instructor} get :self_review_popup, params, session expect(@response).to have_http_status(200) end end end
Results
The total coverage of the test is 75.59%, meeting our minimum coverage requirement.
Related Links
A video of all tests running can be seen here
The main repository can be found here
The forked git repository for this project can be found here
Conclusion
There were 6 modules in the controller for which we wrote unit tests following behavior driven approach. We did not consider any edge cases for now therefore there is scope to further improve the testing coverage to reach 100%.