DeutschLernen/Tests/Unit/Application/Services/QuizQuestionServiceTests.cs
Lasse Rune Hansen 04ad7ef008 feat(backend/domain): add quiz question feature with Docker migration support
- Add QuizQuestion and QuizOption domain entities with QuestionType enum
- Add IQuizQuestionRepository and IQuizOptionRepository interfaces
- Add QuizQuestionRepository and QuizOptionRepository EF Core implementations
- Add QuizQuestionService with CRUD, quiz submission, and statistics
- Add QuizQuestionsController with comprehensive REST API endpoints
- Add QuizQuestionDto and related DTOs for API communication
- Add EF Core migration (20260612050652_AddQuizQuestionTables) for QuizQuestion and QuizOption
- Add seed data with sample quiz questions for Greetings, Numbers, Grammar lessons
- Update AppDbContext with DbSets and entity configurations
- Update Program.cs with migration retry logic for Docker
- Update docker-compose.yml with SeedDatabase configuration
- Add unit tests for QuizQuestionService

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-12 16:49:27 +02:00

321 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using GermanApp.Application.DTOs;
using GermanApp.Application.Services;
using GermanApp.Domain.Entities;
using GermanApp.Domain.Interfaces;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
namespace GermanApp.Tests.Unit.Application.Services;
[TestClass]
public class QuizQuestionServiceTests
{
private Mock<IQuizQuestionRepository> _mockQuizQuestionRepo;
private Mock<IQuizOptionRepository> _mockQuizOptionRepo;
private Mock<ILessonRepository> _mockLessonRepo;
private QuizQuestionService _service;
[TestInitialize]
public void Setup()
{
_mockQuizQuestionRepo = new Mock<IQuizQuestionRepository>();
_mockQuizOptionRepo = new Mock<IQuizOptionRepository>();
_mockLessonRepo = new Mock<ILessonRepository>();
_service = new QuizQuestionService(
_mockQuizQuestionRepo.Object,
_mockQuizOptionRepo.Object,
_mockLessonRepo.Object);
}
[TestMethod]
public async Task GetAllQuizQuestionsAsync_ReturnsMappedDtoList()
{
var lesson = Lesson.Create(1, "Test Lesson", 1, "Test Topic", "Test Description");
var questions = new List<QuizQuestion>
{
QuizQuestion.Create(1, "Question 1", QuestionType.MultipleChoice, "Answer 1", 3, 1)
};
_mockQuizQuestionRepo.Setup(r => r.GetAllOrderedAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(questions);
var result = await _service.GetAllQuizQuestionsAsync();
Assert.AreEqual(1, result.Count);
Assert.AreEqual("Question 1", result[0].QuestionText);
}
[TestMethod]
public async Task GetQuizQuestionByIdAsync_WithExistingId_ReturnsQuestion()
{
var question = QuizQuestion.Create(1, "Test Question", QuestionType.MultipleChoice, "Test Answer", 3, 1);
_mockQuizQuestionRepo.Setup(r => r.GetByIdAsync(1, It.IsAny<CancellationToken>()))
.ReturnsAsync(question);
var result = await _service.GetQuizQuestionByIdAsync(1);
Assert.IsNotNull(result);
Assert.AreEqual("Test Question", result.QuestionText);
}
[TestMethod]
public async Task GetQuizQuestionByIdAsync_WithNonExistingId_ReturnsNull()
{
_mockQuizQuestionRepo.Setup(r => r.GetByIdAsync(999, It.IsAny<CancellationToken>()))
.ReturnsAsync((QuizQuestion?)null);
var result = await _service.GetQuizQuestionByIdAsync(999);
Assert.IsNull(result);
}
[TestMethod]
public async Task GetQuizQuestionsByLessonAsync_ReturnsFilteredQuestions()
{
var questions = new List<QuizQuestion>
{
QuizQuestion.Create(1, "Q1", QuestionType.MultipleChoice, "A1", 3, 1)
};
_mockQuizQuestionRepo.Setup(r => r.GetByLessonAsync(1, It.IsAny<CancellationToken>()))
.ReturnsAsync(questions);
var result = await _service.GetQuizQuestionsByLessonAsync(1);
Assert.AreEqual(1, result.Count);
Assert.AreEqual("Q1", result[0].QuestionText);
}
[TestMethod]
public async Task GetActiveQuizQuestionsByLessonAsync_ReturnsOnlyActive()
{
var activeQuestion = QuizQuestion.Create(1, "Active", QuestionType.MultipleChoice, "A1", 3, 1);
var inactiveQuestion = QuizQuestion.Create(1, "Inactive", QuestionType.MultipleChoice, "A2", 3, 2);
inactiveQuestion.Deactivate();
var questions = new List<QuizQuestion> { activeQuestion, inactiveQuestion };
_mockQuizQuestionRepo.Setup(r => r.GetActiveByLessonAsync(1, It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<QuizQuestion> { activeQuestion });
var result = await _service.GetActiveQuizQuestionsByLessonAsync(1);
Assert.AreEqual(1, result.Count);
Assert.AreEqual("Active", result[0].QuestionText);
}
[TestMethod]
public async Task CreateQuizQuestionAsync_WithValidDtoAndValidLesson_CreatesQuestion()
{
var lesson = Lesson.Create(1, "Test Lesson", 1, "Test Topic", "Test Description");
var options = new List<CreateQuizOptionDto>
{
new CreateQuizOptionDto("Option 1", true, 1),
new CreateQuizOptionDto("Option 2", false, 2)
};
var dto = new CreateQuizQuestionDto(
1, "New Question", QuestionType.MultipleChoice, "Answer 1", 3, 1, options);
var createdQuestion = QuizQuestion.Create(1, "New Question", QuestionType.MultipleChoice, "Answer 1", 3, 1);
_mockLessonRepo.Setup(r => r.GetByIdAsync(1, It.IsAny<CancellationToken>()))
.ReturnsAsync(lesson);
_mockQuizQuestionRepo.Setup(r => r.OrderExistsInLessonAsync(1, 1, null, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
_mockQuizQuestionRepo.Setup(r => r.AddAsync(It.IsAny<QuizQuestion>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(createdQuestion);
_mockQuizQuestionRepo.Setup(r => r.GetByIdAsync(createdQuestion.Id, It.IsAny<CancellationToken>()))
.ReturnsAsync(createdQuestion);
var result = await _service.CreateQuizQuestionAsync(dto);
Assert.IsNotNull(result);
Assert.AreEqual("New Question", result.QuestionText);
}
[TestMethod]
public async Task UpdateQuizQuestionAsync_WithExistingQuestion_UpdatesQuestion()
{
var lesson = Lesson.Create(1, "Test Lesson", 1, "Test Topic", "Test Description");
var existing = QuizQuestion.Create(1, "Old Question", QuestionType.MultipleChoice, "Old Answer", 3, 1);
var options = new List<UpdateQuizOptionDto>
{
new UpdateQuizOptionDto(1, "Updated Option", true, 1)
};
var dto = new UpdateQuizQuestionDto(1, "Updated Question", QuestionType.TrueFalse, "New Answer", 4, 1, true, options);
_mockQuizQuestionRepo.Setup(r => r.GetByIdAsync(1, It.IsAny<CancellationToken>()))
.ReturnsAsync(existing);
_mockLessonRepo.Setup(r => r.GetByIdAsync(1, It.IsAny<CancellationToken>()))
.ReturnsAsync(lesson);
_mockQuizQuestionRepo.Setup(r => r.OrderExistsInLessonAsync(1, 1, 1, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
_mockQuizQuestionRepo.Setup(r => r.UpdateAsync(existing, It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask);
_mockQuizQuestionRepo.Setup(r => r.GetByIdAsync(1, It.IsAny<CancellationToken>()))
.ReturnsAsync(existing);
_mockQuizOptionRepo.Setup(r => r.DeleteByQuestionAsync(1, It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask);
var result = await _service.UpdateQuizQuestionAsync(1, dto);
Assert.IsNotNull(result);
Assert.AreEqual("Updated Question", result.QuestionText);
}
[TestMethod]
public async Task DeleteQuizQuestionAsync_WithExistingQuestion_ReturnsTrue()
{
var question = QuizQuestion.Create(1, "Test Question", QuestionType.MultipleChoice, "Answer", 3, 1);
_mockQuizQuestionRepo.Setup(r => r.GetByIdAsync(1, It.IsAny<CancellationToken>()))
.ReturnsAsync(question);
_mockQuizOptionRepo.Setup(r => r.DeleteByQuestionAsync(1, It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask);
_mockQuizQuestionRepo.Setup(r => r.DeleteAsync(question, It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask);
var result = await _service.DeleteQuizQuestionAsync(1);
Assert.IsTrue(result);
}
[TestMethod]
public async Task GetQuizOptionsByQuestionAsync_ReturnsOptions()
{
var options = new List<QuizOption>
{
QuizOption.Create(1, "Option 1", true, 1),
QuizOption.Create(1, "Option 2", false, 2)
};
_mockQuizOptionRepo.Setup(r => r.GetByQuestionAsync(1, It.IsAny<CancellationToken>()))
.ReturnsAsync(options);
var result = await _service.GetQuizOptionsByQuestionAsync(1);
Assert.AreEqual(2, result.Count);
}
[TestMethod]
public async Task GetCorrectOptionAsync_ReturnsCorrectOption()
{
var option = QuizOption.Create(1, "Correct Option", true, 1);
_mockQuizOptionRepo.Setup(r => r.GetCorrectOptionAsync(1, It.IsAny<CancellationToken>()))
.ReturnsAsync(option);
var result = await _service.GetCorrectOptionAsync(1);
Assert.IsNotNull(result);
Assert.IsTrue(result.IsCorrect);
}
[TestMethod]
public async Task CreateQuizOptionAsync_WithValidData_CreatesOption()
{
var question = QuizQuestion.Create(1, "Test Question", QuestionType.MultipleChoice, "Answer", 3, 1);
var dto = new CreateQuizOptionDto("New Option", true, 1);
var createdOption = QuizOption.Create(1, "New Option", true, 1);
_mockQuizQuestionRepo.Setup(r => r.GetByIdAsync(1, It.IsAny<CancellationToken>()))
.ReturnsAsync(question);
_mockQuizOptionRepo.Setup(r => r.OrderExistsForQuestionAsync(1, 1, null, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
_mockQuizOptionRepo.Setup(r => r.AddAsync(It.IsAny<QuizOption>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(createdOption);
var result = await _service.CreateQuizOptionAsync(1, dto);
Assert.IsNotNull(result);
Assert.AreEqual("New Option", result.Text);
}
[TestMethod]
public async Task SubmitQuizAnswersAsync_WithCorrectAnswers_ReturnsPassedResult()
{
var question = QuizQuestion.Create(1, "What is 2+2?", QuestionType.FillInTheBlank, "4", 3, 1);
var questions = new List<QuizQuestion> { question };
var answers = new List<QuizAnswerDto>
{
new QuizAnswerDto(1, "4", null)
};
var dto = new SubmitQuizAnswersDto(1, answers);
_mockQuizQuestionRepo.Setup(r => r.GetByLessonAsync(1, It.IsAny<CancellationToken>()))
.ReturnsAsync(questions);
var result = await _service.SubmitQuizAnswersAsync(1, dto);
Assert.AreEqual(1, result.TotalQuestions);
Assert.AreEqual(1, result.CorrectAnswers);
Assert.AreEqual(100, result.ScorePercentage);
Assert.IsTrue(result.Passed);
}
[TestMethod]
public async Task SubmitQuizAnswersAsync_WithIncorrectAnswers_ReturnsFailedResult()
{
var question = QuizQuestion.Create(1, "What is 2+2?", QuestionType.FillInTheBlank, "4", 3, 1);
var questions = new List<QuizQuestion> { question };
var answers = new List<QuizAnswerDto>
{
new QuizAnswerDto(1, "5", null) // Wrong answer
};
var dto = new SubmitQuizAnswersDto(1, answers);
_mockQuizQuestionRepo.Setup(r => r.GetByLessonAsync(1, It.IsAny<CancellationToken>()))
.ReturnsAsync(questions);
var result = await _service.SubmitQuizAnswersAsync(1, dto);
Assert.AreEqual(1, result.TotalQuestions);
Assert.AreEqual(0, result.CorrectAnswers);
Assert.AreEqual(0, result.ScorePercentage);
Assert.IsFalse(result.Passed);
}
[TestMethod]
public async Task GetQuizQuestionCountForLessonAsync_ReturnsCount()
{
var questions = new List<QuizQuestion>
{
QuizQuestion.Create(1, "Q1", QuestionType.MultipleChoice, "A1", 3, 1),
QuizQuestion.Create(1, "Q2", QuestionType.MultipleChoice, "A2", 3, 2)
};
_mockQuizQuestionRepo.Setup(r => r.GetActiveByLessonAsync(1, It.IsAny<CancellationToken>()))
.ReturnsAsync(questions);
var result = await _service.GetQuizQuestionCountForLessonAsync(1);
Assert.AreEqual(2, result);
}
[TestMethod]
public async Task GetDifficultyDistributionAsync_ReturnsDistribution()
{
var questions = new List<QuizQuestion>
{
QuizQuestion.Create(1, "Easy", QuestionType.MultipleChoice, "A1", 1, 1),
QuizQuestion.Create(1, "Medium", QuestionType.MultipleChoice, "A2", 3, 2),
QuizQuestion.Create(1, "Hard", QuestionType.MultipleChoice, "A3", 5, 3)
};
_mockQuizQuestionRepo.Setup(r => r.GetActiveByLessonAsync(1, It.IsAny<CancellationToken>()))
.ReturnsAsync(questions);
var result = await _service.GetDifficultyDistributionAsync(1);
Assert.AreEqual(1, result[1]); // Easy
Assert.AreEqual(0, result[2]); // No questions with difficulty 2
Assert.AreEqual(1, result[3]); // Medium
Assert.AreEqual(0, result[4]); // No questions with difficulty 4
Assert.AreEqual(1, result[5]); // Hard
}
}