Skip to content

Commit

Permalink
Merge pull request #1 from ardalis/ardalis/integrationtests
Browse files Browse the repository at this point in the history
Adding Integration Tests using Docker-hosted SQL Server
  • Loading branch information
ardalis committed Sep 20, 2019
2 parents aabc478 + b9f8a6f commit c8d39dd
Show file tree
Hide file tree
Showing 25 changed files with 544 additions and 30 deletions.
2 changes: 2 additions & 0 deletions .dockerignore
@@ -0,0 +1,2 @@
.vs/
.git/
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -116,6 +116,7 @@ _TeamCity*
# Visual Studio code coverage results
*.coverage
*.coveragexml
CodeCoverage/

# NCrunch
_NCrunch_*
Expand Down
22 changes: 22 additions & 0 deletions Ardalis.Specification.sln
Expand Up @@ -11,6 +11,23 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ardalis.Specification.UnitT
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ardalis.Specification", "src\Ardalis.Specification\Ardalis.Specification.csproj", "{5FF21FE4-19B9-4BD2-958D-EAFEDE72D7F8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ardalis.Specification.IntegrationTests", "tests\Ardalis.Specification.IntegrationTests\Ardalis.Specification.IntegrationTests.csproj", "{8543FCF7-ED83-4D74-B47C-CA70F3E1DDE7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docker", "Docker", "{A74A4C02-5F33-4667-A627-78A5B295A61F}"
ProjectSection(SolutionItems) = preProject
.dockerignore = .dockerignore
docker-compose.yml = docker-compose.yml
Dockerfile = Dockerfile
RunTests.bat = RunTests.bat
RunTests.sh = RunTests.sh
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AzurePipeline", "AzurePipeline", "{900638AF-E2F6-4FC9-9A97-E305C23F74FB}"
ProjectSection(SolutionItems) = preProject
azure-pipelines.yml = azure-pipelines.yml
GetCoverage.sh = GetCoverage.sh
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -25,13 +42,18 @@ Global
{5FF21FE4-19B9-4BD2-958D-EAFEDE72D7F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FF21FE4-19B9-4BD2-958D-EAFEDE72D7F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FF21FE4-19B9-4BD2-958D-EAFEDE72D7F8}.Release|Any CPU.Build.0 = Release|Any CPU
{8543FCF7-ED83-4D74-B47C-CA70F3E1DDE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8543FCF7-ED83-4D74-B47C-CA70F3E1DDE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8543FCF7-ED83-4D74-B47C-CA70F3E1DDE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8543FCF7-ED83-4D74-B47C-CA70F3E1DDE7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{85D42214-D223-4F13-9A19-9CFA0D3C1981} = {C35FE7A2-2F7B-4094-AA9F-255B75ED919E}
{5FF21FE4-19B9-4BD2-958D-EAFEDE72D7F8} = {61D9F5ED-0A02-4EEF-A573-31267BB67F0B}
{8543FCF7-ED83-4D74-B47C-CA70F3E1DDE7} = {C35FE7A2-2F7B-4094-AA9F-255B75ED919E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C153A625-42F7-49A7-B99A-6A78B4B866B2}
Expand Down
14 changes: 14 additions & 0 deletions Dockerfile
@@ -0,0 +1,14 @@
FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build
WORKDIR /

COPY . ./
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait
#RUN chmod +x /wait
RUN /bin/bash -c 'ls -la /wait; chmod +x /wait; ls -la /wait'

# install the report generator tool
RUN dotnet tool install dotnet-reportgenerator-globaltool --version 4.2.15 --tool-path /tools

CMD /wait && dotnet test --logger trx --results-directory /var/temp /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura && mv /tests/Ardalis.Specification.UnitTests/coverage.cobertura.xml /var/temp/coverage.unit.cobertura.xml && mv /tests/Ardalis.Specification.IntegrationTests/coverage.cobertura.xml /var/temp/coverage.integration.cobertura.xml && tools/reportgenerator -reports:/var/temp/coverage.*.cobertura.xml -targetdir:/var/temp/coverage -reporttypes:HtmlInline_AzurePipelines\;HTMLChart\;Cobertura


1 change: 1 addition & 0 deletions GetCoverage.bat
@@ -0,0 +1 @@
reportgenerator "-reports:.\TestResults\coverage.integration.cobertura.xml;.\TestResults\coverage.unit.cobertura.xml" "-targetdir:CodeCoverage"
1 change: 1 addition & 0 deletions GetCoverage.sh
@@ -0,0 +1 @@
reportgenerator "-reports:.\TestResults\coverage.integration.cobertura.xml;.\TestResults\coverage.unit.cobertura.xml" "-targetdir:.\CodeCoverage" "-reportTypes:HtmlInline_AzurePipelines;Cobertura"
2 changes: 2 additions & 0 deletions RunTests.bat
@@ -0,0 +1,2 @@
docker-compose build
docker-compose up --abort-on-container-exit
2 changes: 2 additions & 0 deletions RunTests.sh
@@ -0,0 +1,2 @@
docker-compose build
docker-compose up --abort-on-container-exit
63 changes: 39 additions & 24 deletions azure-pipelines.yml
Expand Up @@ -7,7 +7,8 @@ pr:
- master

pool:
vmImage: 'windows-2019'
# vmImage: 'windows-2019'
vmImage: 'ubuntu-16.04'

variables:
buildConfiguration: 'Release'
Expand Down Expand Up @@ -39,10 +40,12 @@ steps:
restoreSolution: '$(solution)'

# Build
- task: VSBuild@1
- task: DotNetCoreCLI@2
displayName: 'Build solution'
inputs:
solution: '$(solution)'
platform: '$(buildPlatform)'
command: 'build'
#solution: '$(solution)'
#platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
arguments: '--output $(Build.ArtifactStagingDirectory)'

Expand All @@ -52,32 +55,44 @@ steps:

# Test with Coverage

# Run all tests with "/p:CollectCoverage=true /p:CoverletOutputFormat=cobertura" to generate the code coverage file
- task: DotNetCoreCLI@2
displayName: dotnet test
inputs:
command: test
arguments: '--configuration $(BuildConfiguration) /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura'
projects: 'tests/**/*.csproj'
nobuild: true
- script: docker-compose up --abort-on-container-exit
displayName: Run Docker Compose Up

- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
displayName: ReportGenerator
inputs:
reports: '$(Build.SourcesDirectory)/tests/**/coverage.cobertura.xml'
targetdir: '$(Build.SourcesDirectory)/CodeCoverage'
reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges'
assemblyfilters: '-xunit*'
# Bug Workaround: https://github.com/microsoft/azure-pipelines-tasks/issues/8762
#- script: sudo mkdir -p $(Build.SourcesDirectory)/TestResults/coverage/summary$(Build.BuildId)
# displayName: Create summary folder

# Publish the code coverage result (summary and web site)
# The summary allows to view the coverage percentage in the summary tab
# The web site allows to view which lines are covered directly in Azure Pipeline
- task: PublishCodeCoverageResults@1
displayName: 'Publish code coverage'

#- script: dir
# workingDirectory: $(Build.SourcesDirectory)
# displayName: List root folder contents

#- script: dir
# workingDirectory: $(Build.SourcesDirectory)/TestResults
# displayName: List root/TestResults folder contents

#- script: dir
# workingDirectory: $(Build.SourcesDirectory)/TestResults/coverage
# displayName: List root/TestResults/coverage folder contents

#- script: chmod 777 . -R
# workingDirectory: $(Build.SourcesDirectory)

- task: PublishTestResults@1
displayName: 'Publish test results'
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: '$(Build.SourcesDirectory)/CodeCoverage/Cobertura.xml'
reportDirectory: '$(Build.SourcesDirectory)/CodeCoverage'
testResultsFormat: VSTest
testResultsFiles: '**/TEST-*.trx'

#- task: PublishCodeCoverageResults@1
# displayName: 'Publish code coverage'
# inputs:
# codeCoverageTool: Cobertura
# summaryFileLocation: '$(Build.SourcesDirectory)/TestResults/coverage/Cobertura.xml'
# reportDirectory: '$(Build.SourcesDirectory)/TestResults/coverage'

- task: CopyFiles@2
displayName: 'Copy *.nupkg Files'
Expand Down
19 changes: 19 additions & 0 deletions docker-compose.yml
@@ -0,0 +1,19 @@
version: '3.4'

services:
tests:
build:
context: .
dockerfile: Dockerfile
environment:
WAIT_HOSTS: database:1433
volumes:
- ./TestResults:/var/temp
depends_on:
- database

database:
image: mcr.microsoft.com/mssql/server:2017-CU8-ubuntu
environment:
SA_PASSWORD: "P@ssW0rd!"
ACCEPT_EULA: "Y"
8 changes: 4 additions & 4 deletions src/Ardalis.Specification/BaseSpecification.cs
Expand Up @@ -13,8 +13,8 @@ protected BaseSpecification(Expression<Func<T, bool>> criteria)
Criteria = criteria;
}
public Expression<Func<T, bool>> Criteria { get; }
public List<Expression<Func<T, object>>> Includes { get; } = new List<Expression<Func<T, object>>>();
public List<string> IncludeStrings { get; } = new List<string>();
public IEnumerable<Expression<Func<T, object>>> Includes { get; } = new List<Expression<Func<T, object>>>();
public IEnumerable<string> IncludeStrings { get; } = new List<string>();
public Expression<Func<T, object>> OrderBy { get; private set; }
public Expression<Func<T, object>> OrderByDescending { get; private set; }
public Expression<Func<T, object>> GroupBy { get; private set; }
Expand All @@ -28,11 +28,11 @@ protected BaseSpecification(Expression<Func<T, bool>> criteria)

protected virtual void AddInclude(Expression<Func<T, object>> includeExpression)
{
Includes.Add(includeExpression);
((List<Expression<Func<T, object>>>)Includes).Add(includeExpression);
}
protected virtual void AddInclude(string includeString)
{
IncludeStrings.Add(includeString);
((List<string>)IncludeStrings).Add(includeString);
}
protected virtual void ApplyPaging(int skip, int take)
{
Expand Down
4 changes: 2 additions & 2 deletions src/Ardalis.Specification/ISpecification.cs
Expand Up @@ -10,8 +10,8 @@ public interface ISpecification<T>
string CacheKey { get; }

Expression<Func<T, bool>> Criteria { get; }
List<Expression<Func<T, object>>> Includes { get; }
List<string> IncludeStrings { get; }
IEnumerable<Expression<Func<T, object>>> Includes { get; }
IEnumerable<string> IncludeStrings { get; }

Expression<Func<T, object>> OrderBy { get; }
Expression<Func<T, object>> OrderByDescending { get; }
Expand Down
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.msbuild" Version="2.3.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="ReportGenerator" Version="4.2.5" />
<PackageReference Include="Dapper" Version="1.60.5" />
<PackageReference Include="FluentAssertions" Version="5.6.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.2.4" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Ardalis.Specification\Ardalis.Specification.csproj" />
</ItemGroup>

</Project>
@@ -0,0 +1,138 @@
using Ardalis.Specification.IntegrationTests.SampleClient;
using Ardalis.Specification.IntegrationTests.SampleSpecs;
using Dapper;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using System.Data.SqlClient;
using System.Linq;
using System.Threading.Tasks;
using Xunit;


namespace Ardalis.Specification.IntegrationTests
{
public class DatabaseCommunicationTests
{
// Run EF Migrations\DBUp script to prepare database before running your tests.
const string ConnectionString = "Data Source=database;Initial Catalog=SampleDatabase;PersistSecurityInfo=True;User ID=sa;Password=P@ssW0rd!";
public SampleDbContext _dbContext;
public EfRepository<Blog> _blogRepository;
public EfRepository<Post> _postRepository;

public DatabaseCommunicationTests()
{
var optionsBuilder = new DbContextOptionsBuilder<SampleDbContext>();
optionsBuilder.UseSqlServer(ConnectionString);
_dbContext = new SampleDbContext(optionsBuilder.Options);

// Run this if you've made seed data or schema changes to force the container to rebuild the db
// _dbContext.Database.EnsureDeleted();

// Note: If the database exists, this will do nothing, so it only creates it once.
// This is fine since these tests all perform read-only operations
_dbContext.Database.EnsureCreated();

_blogRepository = new EfRepository<Blog>(_dbContext);
_postRepository = new EfRepository<Post>(_dbContext);
}

[Fact]
public async Task CanConnectAndRunQuery()
{
using (var conn = new SqlConnection(ConnectionString))
{
await conn.OpenAsync();
const string query = "SELECT 1 AS Data";
var result = (await conn.QueryAsync<int>(query)).FirstOrDefault();
result.Should().Be(1);
}
}

[Fact]
public async Task GetBlogUsingEF()
{
var result = _dbContext.Blogs.FirstOrDefault();

Assert.Equal(BlogBuilder.VALID_BLOG_ID, result.Id);
}

[Fact]
public async Task GetBlogUsingEFRepository()
{
var result = await _blogRepository.GetByIdAsync(BlogBuilder.VALID_BLOG_ID);

result.Should().NotBeNull();
result.Name.Should().Be(BlogBuilder.VALID_BLOG_NAME);
result.Posts.Count.Should().Be(0);
}

[Fact]
public async Task GetBlogUsingEFRepositoryAndSpecShouldIncludePosts()
{
var result = (await _blogRepository.ListAsync(new BlogWithPostsSpec(BlogBuilder.VALID_BLOG_ID))).SingleOrDefault();

result.Should().NotBeNull();
result.Name.Should().Be(BlogBuilder.VALID_BLOG_NAME);
result.Posts.Count.Should().BeGreaterThan(100);
}

[Fact]
public async Task GetBlogUsingEFRepositoryAndSpecWithStringIncludeShouldIncludePosts()
{
var result = (await _blogRepository.ListAsync(new BlogWithPostsUsingStringSpec(BlogBuilder.VALID_BLOG_ID))).SingleOrDefault();

result.Should().NotBeNull();
result.Name.Should().Be(BlogBuilder.VALID_BLOG_NAME);
result.Posts.Count.Should().BeGreaterThan(100);
}

[Fact]
public async Task GetSecondPageOfPostsUsingPostsByBlogPaginatedSpec()
{
int pageSize = 10;
int pageIndex = 1; // page 2
var result = (await _postRepository.ListAsync(new PostsByBlogPaginatedSpec(pageIndex * pageSize, pageSize, BlogBuilder.VALID_BLOG_ID))).ToList();

result.Count.Should().Be(pageSize);
result.First().Id.Should().Be(309);
result.Last().Id.Should().Be(318);
}

[Fact]
public async Task GetPostsWithOrderedSpec()
{
var result = (await _postRepository.ListAsync(new PostsByBlogOrderedSpec(BlogBuilder.VALID_BLOG_ID))).ToList();

result.First().Id.Should().Be(234);
result.Last().Id.Should().Be(399);
}

[Fact]
public async Task GetPostsWithOrderedSpecDescending()
{
var result = (await _postRepository.ListAsync(new PostsByBlogOrderedSpec(BlogBuilder.VALID_BLOG_ID, false))).ToList();

result.First().Id.Should().Be(399);
result.Last().Id.Should().Be(234);
}

// TODO: This could move to the Unit Tests project if specs were in separate project
[Fact]
public async Task EnableCacheShouldSetCacheKeyProperly()
{
var spec = new BlogWithPostsSpec(BlogBuilder.VALID_BLOG_ID);

spec.CacheKey.Should().Be($"BlogWithPostsSpec-{BlogBuilder.VALID_BLOG_ID}");
}

[Fact]
public async Task GroupByShouldWorkProperlyl()
{
var spec = new PostsGroupedByIdSpec();
var result = (await _postRepository.ListAsync(spec)).ToList();

result.First().Id.Should().Be(301);
result.Skip(1).Take(1).First().Id.Should().Be(303);
}
}
}

0 comments on commit c8d39dd

Please sign in to comment.