diff options
author | 杨宇千 <crupest@outlook.com> | 2019-08-17 20:33:01 +0800 |
---|---|---|
committer | 杨宇千 <crupest@outlook.com> | 2019-08-17 20:33:01 +0800 |
commit | 8225c5ff0092f2d666a8c012dffbbc0b428c7d9b (patch) | |
tree | 3127ceaf927c8ae3fb38a5bc25904d2d36973bee | |
parent | 6edc70c00bca22e13ade23472d48d7b940f92eef (diff) | |
download | timeline-8225c5ff0092f2d666a8c012dffbbc0b428c7d9b.tar.gz timeline-8225c5ff0092f2d666a8c012dffbbc0b428c7d9b.tar.bz2 timeline-8225c5ff0092f2d666a8c012dffbbc0b428c7d9b.zip |
Finally solve the database conflict problem in unit tests.
-rw-r--r-- | Timeline.Tests/AuthorizationUnitTest.cs | 11 | ||||
-rw-r--r-- | Timeline.Tests/Helpers/MyWebApplicationFactory.cs | 81 | ||||
-rw-r--r-- | Timeline.Tests/TokenUnitTest.cs | 83 | ||||
-rw-r--r-- | Timeline.Tests/UserUnitTest.cs | 201 | ||||
-rw-r--r-- | Timeline.Tests/UsernameValidatorUnitTest.cs | 23 |
5 files changed, 215 insertions, 184 deletions
diff --git a/Timeline.Tests/AuthorizationUnitTest.cs b/Timeline.Tests/AuthorizationUnitTest.cs index 6f52a12d..4751e95f 100644 --- a/Timeline.Tests/AuthorizationUnitTest.cs +++ b/Timeline.Tests/AuthorizationUnitTest.cs @@ -1,5 +1,6 @@ using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
+using System;
using System.Net;
using System.Threading.Tasks;
using Timeline.Tests.Helpers;
@@ -9,17 +10,23 @@ using Xunit.Abstractions; namespace Timeline.Tests
{
- public class AuthorizationUnitTest : IClassFixture<MyWebApplicationFactory<Startup>>
+ public class AuthorizationUnitTest : IClassFixture<MyWebApplicationFactory<Startup>>, IDisposable
{
private const string AuthorizeUrl = "Test/User/Authorize";
private const string UserUrl = "Test/User/User";
private const string AdminUrl = "Test/User/Admin";
private readonly WebApplicationFactory<Startup> _factory;
+ private readonly Action _disposeAction;
public AuthorizationUnitTest(MyWebApplicationFactory<Startup> factory, ITestOutputHelper outputHelper)
{
- _factory = factory.WithTestLogging(outputHelper);
+ _factory = factory.WithTestConfig(outputHelper, out _disposeAction);
+ }
+
+ public void Dispose()
+ {
+ _disposeAction();
}
[Fact]
diff --git a/Timeline.Tests/Helpers/MyWebApplicationFactory.cs b/Timeline.Tests/Helpers/MyWebApplicationFactory.cs index b49756e4..dfadd1ae 100644 --- a/Timeline.Tests/Helpers/MyWebApplicationFactory.cs +++ b/Timeline.Tests/Helpers/MyWebApplicationFactory.cs @@ -5,6 +5,7 @@ using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
+using System;
using Timeline.Entities;
using Timeline.Services;
using Timeline.Tests.Mock.Data;
@@ -15,69 +16,57 @@ namespace Timeline.Tests.Helpers {
public class MyWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
- // We should keep the connection, so the database is persisted but not recreate every time.
- // See https://docs.microsoft.com/en-us/ef/core/miscellaneous/testing/sqlite#writing-tests .
- private readonly SqliteConnection _databaseConnection;
-
- public MyWebApplicationFactory() : base()
+ protected override void ConfigureWebHost(IWebHostBuilder builder)
{
- _databaseConnection = new SqliteConnection("Data Source=:memory:;");
- _databaseConnection.Open();
-
- InitDatabase();
+ builder.ConfigureTestServices(services =>
+ {
+ services.AddSingleton<IClock, TestClock>();
+ });
}
+ }
- private void InitDatabase()
+ public static class WebApplicationFactoryExtensions
+ {
+ public static WebApplicationFactory<TEntry> WithTestConfig<TEntry>(this WebApplicationFactory<TEntry> factory, ITestOutputHelper outputHelper, out Action disposeAction) where TEntry : class
{
- var options = new DbContextOptionsBuilder<DatabaseContext>()
- .UseSqlite(_databaseConnection)
- .Options;
+ // We should keep the connection, so the database is persisted but not recreate every time.
+ // See https://docs.microsoft.com/en-us/ef/core/miscellaneous/testing/sqlite#writing-tests .
+ SqliteConnection _databaseConnection = new SqliteConnection("Data Source=:memory:;");
+ _databaseConnection.Open();
- using (var context = new DatabaseContext(options))
{
- context.Database.EnsureCreated();
- context.Users.AddRange(MockUsers.Users);
- context.SaveChanges();
- }
- }
+ var options = new DbContextOptionsBuilder<DatabaseContext>()
+ .UseSqlite(_databaseConnection)
+ .Options;
- protected override void ConfigureWebHost(IWebHostBuilder builder)
- {
- builder.ConfigureServices(services =>
- {
- services.AddEntityFrameworkSqlite();
- services.AddDbContext<DatabaseContext>(options =>
+ using (var context = new DatabaseContext(options))
{
- options.UseSqlite(_databaseConnection);
- });
- })
- .ConfigureTestServices(services =>
- {
- services.AddSingleton<IClock, TestClock>();
- });
- }
+ context.Database.EnsureCreated();
+ context.Users.AddRange(MockUsers.Users);
+ context.SaveChanges();
+ };
+ }
- protected override void Dispose(bool disposing)
- {
- if (disposing)
+ disposeAction = () =>
{
_databaseConnection.Close();
_databaseConnection.Dispose();
- }
-
- base.Dispose(disposing);
- }
- }
+ };
- public static class WebApplicationFactoryExtensions
- {
- public static WebApplicationFactory<TEntry> WithTestLogging<TEntry>(this WebApplicationFactory<TEntry> factory, ITestOutputHelper outputHelper) where TEntry : class
- {
return factory.WithWebHostBuilder(builder =>
{
- builder.ConfigureLogging(logging =>
+ builder
+ .ConfigureLogging(logging =>
{
logging.AddXunit(outputHelper);
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddEntityFrameworkSqlite();
+ services.AddDbContext<DatabaseContext>(options =>
+ {
+ options.UseSqlite(_databaseConnection);
+ });
});
});
}
diff --git a/Timeline.Tests/TokenUnitTest.cs b/Timeline.Tests/TokenUnitTest.cs index b5d8a2c8..3babacf7 100644 --- a/Timeline.Tests/TokenUnitTest.cs +++ b/Timeline.Tests/TokenUnitTest.cs @@ -15,43 +15,36 @@ using Xunit.Abstractions; namespace Timeline.Tests
{
- public class TokenUnitTest : IClassFixture<MyWebApplicationFactory<Startup>>
+ public class TokenUnitTest : IClassFixture<MyWebApplicationFactory<Startup>>, IDisposable
{
private const string CreateTokenUrl = "token/create";
private const string VerifyTokenUrl = "token/verify";
private readonly WebApplicationFactory<Startup> _factory;
+ private readonly Action _disposeAction;
public TokenUnitTest(MyWebApplicationFactory<Startup> factory, ITestOutputHelper outputHelper)
{
- _factory = factory.WithTestLogging(outputHelper);
+ _factory = factory.WithTestConfig(outputHelper, out _disposeAction);
}
- [Fact]
- public async void CreateToken_MissingUsername()
+ public void Dispose()
{
- using (var client = _factory.CreateDefaultClient())
- {
- await InvalidModelTestHelpers.TestPostInvalidModel(client, CreateTokenUrl,
- new CreateTokenRequest { Username = null, Password = "user" });
- }
+ _disposeAction();
}
[Fact]
- public async void CreateToken_InvalidModel_MissingPassword()
+ public async void CreateToken_InvalidModel()
{
using (var client = _factory.CreateDefaultClient())
{
+ // missing username
+ await InvalidModelTestHelpers.TestPostInvalidModel(client, CreateTokenUrl,
+ new CreateTokenRequest { Username = null, Password = "user" });
+ // missing password
await InvalidModelTestHelpers.TestPostInvalidModel(client, CreateTokenUrl,
new CreateTokenRequest { Username = "user", Password = null });
- }
- }
-
- [Fact]
- public async void CreateToken_InvalidModel_BadExpireOffset()
- {
- using (var client = _factory.CreateDefaultClient())
- {
+ // bad expire offset
await InvalidModelTestHelpers.TestPostInvalidModel(client, CreateTokenUrl,
new CreateTokenRequest
{
@@ -101,10 +94,11 @@ namespace Timeline.Tests }
[Fact]
- public async void VerifyToken_InvalidModel_MissingToken()
+ public async void VerifyToken_InvalidModel()
{
using (var client = _factory.CreateDefaultClient())
{
+ // missing token
await InvalidModelTestHelpers.TestPostInvalidModel(client, VerifyTokenUrl,
new VerifyTokenRequest { Token = null });
}
@@ -122,43 +116,42 @@ namespace Timeline.Tests }
[Fact]
- public async void VerifyToken_BadVersion_AND_UserNotExist()
+ public async void VerifyToken_BadVersion()
{
using (var client = _factory.CreateDefaultClient())
{
+ var token = (await client.CreateUserTokenAsync(MockUsers.UserUsername, MockUsers.UserPassword)).Token;
+
using (var scope = _factory.Server.Host.Services.CreateScope()) // UserService is scoped.
{
// create a user for test
var userService = scope.ServiceProvider.GetRequiredService<IUserService>();
+ await userService.PatchUser(MockUsers.UserUsername, null, null);
+ }
- const string username = "verifytokentest0";
- const string password = "12345678";
-
- await userService.PutUser(username, password, false);
-
- // create a token
- var token = (await client.CreateUserTokenAsync(username, password)).Token;
-
- // increase version
- await userService.PatchUser(username, null, null);
-
- // test against bad version
- var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = token });
- response.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(TokenController.ErrorCodes.Verify_BadVersion);
-
-
- // create another token
- var token2 = (await client.CreateUserTokenAsync(username, password)).Token;
+ var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = token });
+ response.Should().HaveStatusCodeBadRequest()
+ .And.Should().HaveBodyAsCommonResponseWithCode(TokenController.ErrorCodes.Verify_BadVersion);
+ }
+ }
- // delete user
- await userService.DeleteUser(username);
+ [Fact]
+ public async void VerifyToken_UserNotExist()
+ {
+ using (var client = _factory.CreateDefaultClient())
+ {
+ var token = (await client.CreateUserTokenAsync(MockUsers.UserUsername, MockUsers.UserPassword)).Token;
- // test against user not exist
- var response2 = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = token });
- response2.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(TokenController.ErrorCodes.Verify_UserNotExist);
+ using (var scope = _factory.Server.Host.Services.CreateScope()) // UserService is scoped.
+ {
+ // create a user for test
+ var userService = scope.ServiceProvider.GetRequiredService<IUserService>();
+ await userService.DeleteUser(MockUsers.UserUsername);
}
+
+ var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = token });
+ response.Should().HaveStatusCodeBadRequest()
+ .And.Should().HaveBodyAsCommonResponseWithCode(TokenController.ErrorCodes.Verify_UserNotExist);
}
}
diff --git a/Timeline.Tests/UserUnitTest.cs b/Timeline.Tests/UserUnitTest.cs index 2aa89fe3..7bf12ad8 100644 --- a/Timeline.Tests/UserUnitTest.cs +++ b/Timeline.Tests/UserUnitTest.cs @@ -1,5 +1,6 @@ using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
+using System;
using System.Net.Http;
using System.Threading.Tasks;
using Timeline.Controllers;
@@ -13,13 +14,19 @@ using Xunit.Abstractions; namespace Timeline.Tests
{
- public class UserUnitTest : IClassFixture<MyWebApplicationFactory<Startup>>
+ public class UserUnitTest : IClassFixture<MyWebApplicationFactory<Startup>>, IDisposable
{
private readonly WebApplicationFactory<Startup> _factory;
+ private readonly Action _disposeAction;
public UserUnitTest(MyWebApplicationFactory<Startup> factory, ITestOutputHelper outputHelper)
{
- _factory = factory.WithTestLogging(outputHelper);
+ _factory = factory.WithTestConfig(outputHelper, out _disposeAction);
+ }
+
+ public void Dispose()
+ {
+ _disposeAction();
}
[Fact]
@@ -28,9 +35,8 @@ namespace Timeline.Tests using (var client = await _factory.CreateClientAsAdmin())
{
var res = await client.GetAsync("users");
- // Because tests are running asyncronized. So database may be modified and
- // we can't check the exact user lists at this point. So only check the format.
- res.Should().HaveStatusCodeOk().And.Should().HaveBodyAsJson<UserInfo[]>();
+ res.Should().HaveStatusCodeOk().And.Should().HaveBodyAsJson<UserInfo[]>()
+ .Which.Should().BeEquivalentTo(MockUsers.UserInfos);
}
}
@@ -58,118 +64,156 @@ namespace Timeline.Tests }
[Fact]
- public async Task Put_Patch_Delete_User()
+ public async Task Put_InvalidModel()
{
using (var client = await _factory.CreateClientAsAdmin())
{
- const string username = "putpatchdeleteuser";
- const string password = "password";
- const string url = "users/" + username;
-
- // Put Invalid Model
+ const string url = "users/aaaaaaaa";
+ // missing password
await InvalidModelTestHelpers.TestPutInvalidModel(client, url, new UserPutRequest { Password = null, Administrator = false });
- await InvalidModelTestHelpers.TestPutInvalidModel(client, url, new UserPutRequest { Password = password, Administrator = null });
+ // missing administrator
+ await InvalidModelTestHelpers.TestPutInvalidModel(client, url, new UserPutRequest { Password = "???", Administrator = null });
+ }
+ }
- async Task CheckAdministrator(bool administrator)
+ [Fact]
+ public async Task Put_BadUsername()
+ {
+ using (var client = await _factory.CreateClientAsAdmin())
+ {
+ var res = await client.PutAsJsonAsync("users/dsf fddf", new UserPutRequest
{
- var res = await client.GetAsync(url);
- res.Should().HaveStatusCodeOk()
- .And.Should().HaveBodyAsJson<UserInfo>()
- .Which.Administrator.Should().Be(administrator);
- }
+ Password = "???",
+ Administrator = false
+ });
+ res.Should().HaveStatusCodeBadRequest()
+ .And.Should().HaveBodyAsCommonResponseWithCode(UserController.ErrorCodes.Put_BadUsername);
+ }
+ }
- {
- // Put Bad Username.
- var res = await client.PutAsJsonAsync("users/dsf fddf", new UserPutRequest
- {
- Password = password,
- Administrator = false
- });
- res.Should().HaveStatusCodeBadRequest()
- .And.Should().HaveBodyAsCommonResponseWithCode(UserController.ErrorCodes.Put_BadUsername);
- }
+ private async Task CheckAdministrator(HttpClient client, string username, bool administrator)
+ {
+ var res = await client.GetAsync("users/" + username);
+ res.Should().HaveStatusCodeOk()
+ .And.Should().HaveBodyAsJson<UserInfo>()
+ .Which.Administrator.Should().Be(administrator);
+ }
+ [Fact]
+ public async Task Put_Modiefied()
+ {
+ using (var client = await _factory.CreateClientAsAdmin())
+ {
+ var res = await client.PutAsJsonAsync("users/" + MockUsers.UserUsername, new UserPutRequest
{
- // Put Created.
- var res = await client.PutAsJsonAsync(url, new UserPutRequest
- {
- Password = password,
- Administrator = false
- });
- res.Should().BePutCreated();
- await CheckAdministrator(false);
- }
+ Password = "password",
+ Administrator = false
+ });
+ res.Should().BePutModified();
+ await CheckAdministrator(client, MockUsers.UserUsername, false);
+ }
+ }
- {
- // Put Modified.
- var res = await client.PutAsJsonAsync(url, new UserPutRequest
- {
- Password = password,
- Administrator = true
- });
- res.Should().BePutModified();
- await CheckAdministrator(true);
- }
+ [Fact]
+ public async Task Put_Created()
+ {
+ using (var client = await _factory.CreateClientAsAdmin())
+ {
+ const string username = "puttest";
+ const string url = "users/" + username;
- // Patch Not Exist
+ var res = await client.PutAsJsonAsync(url, new UserPutRequest
{
- var res = await client.PatchAsJsonAsync("users/usernotexist", new UserPatchRequest { });
- res.Should().HaveStatusCodeNotFound()
- .And.Should().HaveBodyAsCommonResponseWithCode(UserController.ErrorCodes.Patch_NotExist);
- }
+ Password = "password",
+ Administrator = false
+ });
+ res.Should().BePutCreated();
+ await CheckAdministrator(client, username, false);
+ }
+ }
- // Patch Success
+ [Fact]
+ public async Task Patch_NotExist()
+ {
+ using (var client = await _factory.CreateClientAsAdmin())
+ {
+ var res = await client.PatchAsJsonAsync("users/usernotexist", new UserPatchRequest { });
+ res.Should().HaveStatusCodeNotFound()
+ .And.Should().HaveBodyAsCommonResponseWithCode(UserController.ErrorCodes.Patch_NotExist);
+ }
+ }
+
+ [Fact]
+ public async Task Patch_Success()
+ {
+ using (var client = await _factory.CreateClientAsAdmin())
+ {
{
- var res = await client.PatchAsJsonAsync(url, new UserPatchRequest { Administrator = false });
+ var res = await client.PatchAsJsonAsync("users/" + MockUsers.UserUsername,
+ new UserPatchRequest { Administrator = false });
res.Should().HaveStatusCodeOk();
- await CheckAdministrator(false);
+ await CheckAdministrator(client, MockUsers.UserUsername, false);
}
+ }
+ }
- // Delete Deleted
+ [Fact]
+ public async Task Delete_Deleted()
+ {
+ using (var client = await _factory.CreateClientAsAdmin())
+ {
{
+ var url = "users/" + MockUsers.UserUsername;
var res = await client.DeleteAsync(url);
res.Should().BeDeleteDeleted();
var res2 = await client.GetAsync(url);
res2.Should().HaveStatusCodeNotFound();
}
+ }
+ }
- // Delete Not Exist
+ [Fact]
+ public async Task Delete_NotExist()
+ {
+ using (var client = await _factory.CreateClientAsAdmin())
+ {
{
- var res = await client.DeleteAsync(url);
+ var res = await client.DeleteAsync("users/usernotexist");
res.Should().BeDeleteNotExist();
}
}
}
-
- public class ChangePasswordUnitTest : IClassFixture<MyWebApplicationFactory<Startup>>
+ public class ChangePasswordUnitTest : IClassFixture<MyWebApplicationFactory<Startup>>, IDisposable
{
private const string url = "userop/changepassword";
private readonly WebApplicationFactory<Startup> _factory;
+ private readonly Action _disposeAction;
public ChangePasswordUnitTest(MyWebApplicationFactory<Startup> factory, ITestOutputHelper outputHelper)
{
- _factory = factory.WithTestLogging(outputHelper);
+ _factory = factory.WithTestConfig(outputHelper, out _disposeAction);
}
-
- [Fact]
- public async Task InvalidModel_OldPassword()
+ public void Dispose()
{
- using (var client = await _factory.CreateClientAsUser())
- {
- await InvalidModelTestHelpers.TestPostInvalidModel(client, url, new ChangePasswordRequest { OldPassword = null, NewPassword = "???" });
- }
+ _disposeAction();
}
+
[Fact]
- public async Task InvalidModel_NewPassword()
+ public async Task InvalidModel()
{
using (var client = await _factory.CreateClientAsUser())
{
- await InvalidModelTestHelpers.TestPostInvalidModel(client, url, new ChangePasswordRequest { OldPassword = "???", NewPassword = null });
+ // missing old password
+ await InvalidModelTestHelpers.TestPostInvalidModel(client, url,
+ new ChangePasswordRequest { OldPassword = null, NewPassword = "???" });
+ // missing new password
+ await InvalidModelTestHelpers.TestPostInvalidModel(client, url,
+ new ChangePasswordRequest { OldPassword = "???", NewPassword = null });
}
}
@@ -187,22 +231,13 @@ namespace Timeline.Tests [Fact]
public async Task Success()
{
- const string username = "changepasswordtest";
- const string password = "password";
-
- // create a new user to avoid interference
- using (var client = await _factory.CreateClientAsAdmin())
- {
- var res = await client.PutAsJsonAsync("users/" + username, new UserPutRequest { Password = password, Administrator = false });
- res.Should().BePutCreated();
- }
-
- using (var client = await _factory.CreateClientWithCredential(username, password))
+ using (var client = await _factory.CreateClientAsUser())
{
const string newPassword = "new";
- var res = await client.PostAsJsonAsync(url, new ChangePasswordRequest { OldPassword = password, NewPassword = newPassword });
+ var res = await client.PostAsJsonAsync(url,
+ new ChangePasswordRequest { OldPassword = MockUsers.UserPassword, NewPassword = newPassword });
res.Should().HaveStatusCodeOk();
- await client.CreateUserTokenAsync(username, newPassword);
+ await client.CreateUserTokenAsync(MockUsers.UserUsername, newPassword);
}
}
}
diff --git a/Timeline.Tests/UsernameValidatorUnitTest.cs b/Timeline.Tests/UsernameValidatorUnitTest.cs index 450564b7..20558d0e 100644 --- a/Timeline.Tests/UsernameValidatorUnitTest.cs +++ b/Timeline.Tests/UsernameValidatorUnitTest.cs @@ -14,13 +14,6 @@ namespace Timeline.Tests _validator = validator;
}
- [Fact]
- public void NullShouldThrow()
- {
- _validator.Invoking(v => v.Validate(null, out string message)).Should().Throw<ArgumentNullException>();
- }
-
-
private string FailAndMessage(string username)
{
var result = _validator.Validate(username, out var message);
@@ -31,7 +24,21 @@ namespace Timeline.Tests private void Succeed(string username)
{
_validator.Validate(username, out var message).Should().BeTrue();
- message.Should().BeNull();
+ message.Should().Be(ValidationConstants.SuccessMessage);
+ }
+
+ [Fact]
+ public void Null()
+ {
+ FailAndMessage(null).Should().ContainEquivalentOf("null");
+ }
+
+ [Fact]
+ public void NotString()
+ {
+ var result = _validator.Validate(123, out var message);
+ result.Should().BeFalse();
+ message.Should().ContainEquivalentOf("type");
}
[Fact]
|