Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code coverage improvements #1357

Merged
merged 3 commits into from
Oct 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,13 @@ jobs:
dotnet build --no-restore --configuration Release /p:VersionSuffix=$env:PACKAGE_VERSION_SUFFIX
- name: Test
run: |
dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage" --logger "GitHubActions;summary.includeSkippedTests=true" -- RunConfiguration.CollectSourceInformation=true DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.DeterministicReport=true
dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage" --logger "GitHubActions;summary.includeSkippedTests=true"
- name: Upload coverage to codecov.io
if: matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v3
with:
fail_ci_if_error: true
verbose: true
- name: Generate packages
shell: pwsh
run: |
Expand Down
5 changes: 4 additions & 1 deletion Build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ Write-Host "$(pwsh --version)"
Write-Host "Active .NET SDK: $(dotnet --version)"
Write-Host "Using version suffix: $versionSuffix"

Remove-Item -Recurse -Force artifacts -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force * -Include coverage.cobertura.xml

dotnet tool restore
VerifySuccessExitCode

dotnet build --configuration Release /p:VersionSuffix=$versionSuffix
VerifySuccessExitCode

dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.DeterministicReport=true
dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage"
VerifySuccessExitCode

dotnet reportgenerator -reports:**\coverage.cobertura.xml -targetdir:artifacts\coverage -filefilters:-*.g.cs
Expand Down
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
<IsPackable>false</IsPackable>
<WarnOnPackingNonPackableProject>false</WarnOnPackingNonPackableProject>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodingGuidelines.ruleset</CodeAnalysisRuleSet>
<RunSettingsFilePath>$(MSBuildThisFileDirectory)tests.runsettings</RunSettingsFilePath>
<JsonApiDotNetCoreVersionPrefix>5.4.1</JsonApiDotNetCoreVersionPrefix>
</PropertyGroup>
</Project>
1 change: 1 addition & 0 deletions JsonApiDotNetCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
CodingGuidelines.ruleset = CodingGuidelines.ruleset
CSharpGuidelinesAnalyzer.config = CSharpGuidelinesAnalyzer.config
Directory.Build.props = Directory.Build.props
tests.runsettings = tests.runsettings
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{026FBC6C-AF76-4568-9B87-EC73457899FD}"
Expand Down
229 changes: 229 additions & 0 deletions test/NoEntityFrameworkTests/TagTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
using System.Net;
using System.Text.Json;
using FluentAssertions;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Serialization.Objects;
using Microsoft.Extensions.DependencyInjection;
using NoEntityFrameworkExample.Models;
using TestBuildingBlocks;
using Xunit;

namespace NoEntityFrameworkTests;

public sealed class TagTests : IntegrationTest, IClassFixture<NoLoggingWebApplicationFactory<Tag>>
{
private readonly NoLoggingWebApplicationFactory<Tag> _factory;

protected override JsonSerializerOptions SerializerOptions
{
get
{
var options = _factory.Services.GetRequiredService<IJsonApiOptions>();
return options.SerializerOptions;
}
}

public TagTests(NoLoggingWebApplicationFactory<Tag> factory)
{
_factory = factory;
}

[Fact]
public async Task Can_get_primary_resources()
{
// Arrange
const string route = "/api/tags";

// Act
(HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync<Document>(route);

// Assert
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

responseDocument.Data.ManyValue.ShouldHaveCount(3);

responseDocument.Meta.Should().ContainTotal(3);
}

[Fact]
public async Task Can_filter_in_primary_resources()
{
// Arrange
const string route = "/api/tags?filter=equals(name,'Personal')";

// Act
(HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync<Document>(route);

// Assert
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

responseDocument.Data.ManyValue.ShouldHaveCount(1);
responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("name").With(value => value.Should().Be("Personal"));

responseDocument.Meta.Should().ContainTotal(1);
}

[Fact]
public async Task Can_filter_in_related_resources()
{
// Arrange
const string route = "/api/tags?filter=has(todoItems,equals(description,'Check emails'))";

// Act
(HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync<Document>(route);

// Assert
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

responseDocument.Data.ManyValue.ShouldHaveCount(1);
responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("name").With(value => value.Should().Be("Business"));

responseDocument.Meta.Should().ContainTotal(1);
}

[Fact]
public async Task Can_sort_on_attribute_in_primary_resources()
{
// Arrange
const string route = "/api/tags?sort=-id";

// Act
(HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync<Document>(route);

// Assert
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

responseDocument.Data.ManyValue.ShouldHaveCount(3);
responseDocument.Data.ManyValue[0].Id.Should().Be("3");
responseDocument.Data.ManyValue[1].Id.Should().Be("2");
responseDocument.Data.ManyValue[2].Id.Should().Be("1");
}

[Fact]
public async Task Can_sort_on_count_in_primary_resources()
{
// Arrange
const string route = "/api/tags?sort=-count(todoItems),id";

// Act
(HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync<Document>(route);

// Assert
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

responseDocument.Data.ManyValue.ShouldHaveCount(3);
responseDocument.Data.ManyValue[0].Id.Should().Be("1");
responseDocument.Data.ManyValue[1].Id.Should().Be("2");
responseDocument.Data.ManyValue[2].Id.Should().Be("3");
}

[Fact]
public async Task Can_paginate_in_primary_resources()
{
// Arrange
const string route = "/api/tags?page[size]=1&page[number]=2&sort=id";

// Act
(HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync<Document>(route);

// Assert
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

responseDocument.Data.ManyValue.ShouldHaveCount(1);
responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("name").With(value => value.Should().Be("Family"));

responseDocument.Meta.Should().ContainTotal(3);
}

[Fact]
public async Task Can_select_fields_in_primary_resources()
{
// Arrange
const string route = "/api/tags?fields[tags]=todoItems";

// Act
(HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync<Document>(route);

// Assert
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

responseDocument.Data.ManyValue.ShouldNotBeEmpty();
responseDocument.Data.ManyValue.Should().AllSatisfy(resource => resource.Attributes.Should().BeNull());
responseDocument.Data.ManyValue.Should().AllSatisfy(resource => resource.Relationships.ShouldOnlyContainKeys("todoItems"));
}

[Fact]
public async Task Can_include_in_primary_resources()
{
// Arrange
const string route = "/api/tags?include=todoItems.owner";

// Act
(HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync<Document>(route);

// Assert
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

responseDocument.Data.ManyValue.Should().NotBeEmpty();
responseDocument.Included.Should().NotBeEmpty();
}

[Fact]
public async Task Can_get_primary_resource()
{
// Arrange
const string route = "/api/tags/1";

// Act
(HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync<Document>(route);

// Assert
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

responseDocument.Data.SingleValue.ShouldNotBeNull();
responseDocument.Data.SingleValue.Id.Should().Be("1");
}

[Fact]
public async Task Can_get_secondary_resources()
{
// Arrange
const string route = "/api/tags/1/todoItems?sort=id";

// Act
(HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync<Document>(route);

// Assert
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

responseDocument.Data.ManyValue.ShouldHaveCount(3);
responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("description").With(value => value.Should().Be("Make homework"));
responseDocument.Data.ManyValue[1].Attributes.ShouldContainKey("description").With(value => value.Should().Be("Book vacation"));
responseDocument.Data.ManyValue[2].Attributes.ShouldContainKey("description").With(value => value.Should().Be("Cook dinner"));

responseDocument.Meta.Should().ContainTotal(3);
}

[Fact]
public async Task Can_get_ToMany_relationship()
{
// Arrange
const string route = "/api/tags/2/relationships/todoItems";

// Act
(HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync<Document>(route);

// Assert
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);

responseDocument.Data.ManyValue.ShouldHaveCount(1);
responseDocument.Data.ManyValue[0].Id.Should().Be("3");

responseDocument.Meta.Should().ContainTotal(1);
}

protected override HttpClient CreateClient()
{
return _factory.CreateClient();
}
}
2 changes: 1 addition & 1 deletion test/TestBuildingBlocks/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Diagnostics.CodeAnalysis;

// https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/MSBuildIntegration.md#excluding-from-coverage
// Justification: This assembly contains building blocks for writing tests. It does not contain code that ships.
[assembly: ExcludeFromCodeCoverage]
16 changes: 16 additions & 0 deletions tests.runsettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<RunConfiguration>
<CollectSourceInformation>true</CollectSourceInformation>
</RunConfiguration>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="XPlat Code Coverage">
<Configuration>
<ExcludeByAttribute>ObsoleteAttribute,GeneratedCodeAttribute</ExcludeByAttribute>
<DeterministicReport>true</DeterministicReport>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>