Put interface behind database provider for testing. Use state object to get data from database, then map to model.

This commit is contained in:
Robert Marshall 2019-04-14 21:49:01 +01:00
parent 5a0d0933ce
commit 20ccdd07f1
11 changed files with 118 additions and 34 deletions

View file

@ -0,0 +1,32 @@
using Website.Data;
using Xunit;
using FluentAssertions;
using MySql.Data.MySqlClient;
using NSubstitute;
using Microsoft.Extensions.Configuration;
namespace Website.Tests.Data
{
public class MySQLDatabaseProviderTests
{
const string ConnectionString = "Server=host;User ID=username;Password=password;Database=database";
[Fact]
public void NewConnection_WithStringConstructor_ReturnsMySQLConnection() {
var provider = new MySQLDatabaseProvider(ConnectionString);
var connection = provider.NewConnection();
connection.Should().BeOfType<MySqlConnection>();
(connection as MySqlConnection).ConnectionString.Should().Be(ConnectionString);
}
[Fact]
public void NewConnection_WithConfigConstructor_ReturnsMySQLConnection() {
var config = Substitute.For<IConfiguration>();
config.GetConnectionString("database").Returns(ConnectionString);
var provider = new MySQLDatabaseProvider(ConnectionString);
var connection = provider.NewConnection();
connection.Should().BeOfType<MySqlConnection>();
(connection as MySqlConnection).ConnectionString.Should().Be(ConnectionString);
}
}
}

View file

@ -12,7 +12,9 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="fluentassertions" Version="5.6.0" /> <PackageReference Include="fluentassertions" Version="5.6.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
<PackageReference Include="mysqlconnector" Version="0.52.0" />
<PackageReference Include="nsubstitute" Version="4.0.0" /> <PackageReference Include="nsubstitute" Version="4.0.0" />
<PackageReference Include="xunit" Version="2.4.0" /> <PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />

View file

@ -2,21 +2,22 @@ using System.Threading.Tasks;
using System.Linq; using System.Linq;
using Dapper; using Dapper;
using Website.Models; using Website.Models;
using Website.Data.States;
namespace Website.Data namespace Website.Data
{ {
public class BlogRepository public class BlogRepository
{ {
private readonly DatabaseProvider _dbProvider; private readonly IDatabaseProvider _dbProvider;
public BlogRepository(DatabaseProvider dbProvider) => _dbProvider = dbProvider; public BlogRepository(IDatabaseProvider dbProvider) => _dbProvider = dbProvider;
public async Task<BlogPost> GetPostAsync(int id) { public async Task<BlogPost> GetPostAsync(int id) {
const string query = "SELECT * FROM blog_posts WHERE post_id=@id"; const string query = "SELECT * FROM blog_posts WHERE post_id=@id AND post_deleted=0";
using (var connection = _dbProvider.NewConnection()) { using (var connection = _dbProvider.NewConnection()) {
connection.Open(); connection.Open();
var result = await connection.QueryAsync<BlogPost>(query, new{id}); var result = await connection.QueryAsync<BlogPostState>(query, new{id});
return result.FirstOrDefault(); return new BlogPost(result.First());
} }
} }
} }

View file

@ -1,17 +0,0 @@
using System.Data;
using Microsoft.Extensions.Configuration;
using MySql.Data.MySqlClient;
namespace Website.Data
{
public class DatabaseProvider
{
private readonly string _connectionString;
public DatabaseProvider(IConfiguration config) => _connectionString = config.GetConnectionString("database");
public DatabaseProvider(string connectionString) => _connectionString = connectionString;
public IDbConnection NewConnection() => new MySqlConnection(_connectionString);
}
}

View file

@ -0,0 +1,9 @@
using System.Data;
namespace Website.Data
{
public interface IDatabaseProvider
{
IDbConnection NewConnection();
}
}

View file

@ -0,0 +1,17 @@
using System.Data;
using Microsoft.Extensions.Configuration;
using MySql.Data.MySqlClient;
namespace Website.Data
{
public class MySQLDatabaseProvider:IDatabaseProvider
{
private readonly string _connectionString;
public MySQLDatabaseProvider(IConfiguration config) => _connectionString = config.GetConnectionString("database");
public MySQLDatabaseProvider(string connectionString) => _connectionString = connectionString;
public IDbConnection NewConnection() => new MySqlConnection(_connectionString);
}
}

View file

@ -0,0 +1,19 @@
using System;
using Dapper.Contrib.Extensions;
namespace Website.Data.States
{
[Table("blog_posts")]
public class BlogPostState
{
[Key]
public int Post_Id { get; set; }
public string Post_Title { get; set; }
public string Post_Content { get; set; }
public DateTime Post_Timestamp { get; set; }
public string Post_Draft { get; set; }
public string Post_Url { get; set; }
public bool Post_Deleted{ get; set; }
public int User_Id { get; set; }
}
}

View file

@ -1,9 +1,27 @@
using System;
using Website.Data.States;
namespace Website.Models namespace Website.Models
{ {
public class BlogPost public class BlogPost
{ {
public int PostId { get; set; } public BlogPost(BlogPostState state)
public string PostTitle { get; set; } {
public string Post_Title { get; set; } Id = state.Post_Id;
Title = state.Post_Title;
Content = state.Post_Content;
Timestamp = state.Post_Timestamp;
Draft = state.Post_Draft;
Url = state.Post_Url;
UserId = state.User_Id;
}
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public DateTime Timestamp { get; set; }
public string Draft { get; set; }
public string Url { get; set; }
public int UserId { get; set; }
} }
} }

View file

@ -39,7 +39,7 @@ namespace Website
} }
private void RegisterRepositories(IServiceCollection services) => private void RegisterRepositories(IServiceCollection services) =>
services.AddSingleton<DatabaseProvider, DatabaseProvider>() services.AddSingleton<IDatabaseProvider, MySQLDatabaseProvider>()
.AddSingleton<BlogRepository, BlogRepository>(); .AddSingleton<BlogRepository, BlogRepository>();
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View file

@ -5,4 +5,4 @@
ViewData["Title"] = "Blog"; ViewData["Title"] = "Blog";
} }
@Model.PostTitle @Model.Title

View file

@ -5,5 +5,8 @@
"System": "Information", "System": "Information",
"Microsoft": "Information" "Microsoft": "Information"
} }
},
"ConnectionStrings": {
"database": "Server=localhost;User ID=user;Password=pass;Database=db"
} }
} }