如何对void返回类型的Create方法做单元测试?是否需修改返回类型?
Hey there! Great question—void methods absolutely can be unit tested effectively without changing their return type. Let’s break down how to approach this, along with whether adjusting the return type makes sense.
1. What to Test for Your Create Method
Your Create method has clear responsibilities, so we can test around those behaviors instead of relying on a return value:
- Exception scenarios: Verify that the correct
CusExceptionis thrown when invalid inputs are provided (null car, existing login, existing email). - Dependency interactions: Ensure that the right methods on your dependencies (
ICarQueryandICarRepository) are called at the right times, with the right arguments. - Validation flow: Confirm that all pre-checks (login/email existence) are executed when a valid car is provided.
2. Is Changing Return Type to bool Reasonable?
Short answer: Probably not necessary, just for testing.
Your current design uses exceptions to signal errors, which is a valid approach—exceptions carry detailed error context (like your Error enum and message) and force callers to handle failure cases explicitly. Changing to a bool would lose that context, and require callers to guess why the operation failed.
If your business logic specifically needs to return a success/failure status (with details), that’s a different conversation—but purely for testing, modifying the method’s signature is an unnecessary workaround.
3. Practical Unit Test Examples (Using Moq)
Let’s use Moq (a popular .NET mocking framework) to write tests covering all key scenarios.
Test 1: Null Car Throws Exception
[Test] public void Create_WithNullCar_ThrowsCusException() { // Arrange var mockCarQuery = new Mock<ICarQuery>(); var mockCarRepo = new Mock<ICarRepository>(); var service = new CreateCarService(mockCarQuery.Object, mockCarRepo.Object); // Act & Assert Assert.Throws<CusException>(() => service.Create(null)); }
Test 2: Existing Login Throws Exception
[Test] public void Create_WithExistingLogin_ThrowsCusException() { // Arrange var testCar = new Car { Login = "already-taken", Email = "test@example.com" }; var mockCarQuery = new Mock<ICarQuery>(); mockCarQuery.Setup(q => q.IsLoginExist(testCar.Login)).Returns(true); var mockCarRepo = new Mock<ICarRepository>(); var service = new CreateCarService(mockCarQuery.Object, mockCarRepo.Object); // Act & Assert var exception = Assert.Throws<CusException>(() => service.Create(testCar)); Assert.AreEqual("Message1", exception.Message); // You can also verify the Error enum value here if needed }
Test 3: Valid Car Triggers Repository Add
[Test] public void Create_WithValidCar_CallsRepositoryAddOnce() { // Arrange var testCar = new Car { Login = "new-user", Email = "new@example.com" }; var mockCarQuery = new Mock<ICarQuery>(); mockCarQuery.Setup(q => q.IsLoginExist(testCar.Login)).Returns(false); mockCarQuery.Setup(q => q.IsEmailExist(testCar.Email)).Returns(false); var mockCarRepo = new Mock<ICarRepository>(); var service = new CreateCarService(mockCarQuery.Object, mockCarRepo.Object); // Act service.Create(testCar); // Assert mockCarRepo.Verify(repo => repo.Add(testCar), Times.Once); }
Test 4: Valid Car Runs Both Existence Checks
[Test] public void Create_WithValidCar_ExecutesBothExistenceChecks() { // Arrange var testCar = new Car { Login = "new-user", Email = "new@example.com" }; var mockCarQuery = new Mock<ICarQuery>(); mockCarQuery.Setup(q => q.IsLoginExist(testCar.Login)).Returns(false); mockCarQuery.Setup(q => q.IsEmailExist(testCar.Email)).Returns(false); var mockCarRepo = new Mock<ICarRepository>(); var service = new CreateCarService(mockCarQuery.Object, mockCarRepo.Object); // Act service.Create(testCar); // Assert mockCarQuery.Verify(q => q.IsLoginExist(testCar.Login), Times.Once); mockCarQuery.Verify(q => q.IsEmailExist(testCar.Email), Times.Once); }
Wrapping Up
By focusing on behavior (exceptions thrown, dependencies called) instead of return values, you can fully test your void method without altering its original design. This keeps your code clean and your tests focused on what the method is actually supposed to do.
内容的提问来源于stack exchange,提问作者Arie

