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

Use WebApplicationFactory as the base class for Web integration testing. #17497

Merged
merged 5 commits into from
Sep 5, 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
19 changes: 3 additions & 16 deletions docs/en/UI/AspNetCore/Testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,23 +198,10 @@ ABP Framework doesn't provide any infrastructure to test your JavaScript code. Y

> Volo.Abp.AspNetCore.TestBase package is already installed in the `.Web.Tests` project.

This package provides the `AbpAspNetCoreIntegratedTestBase` as the fundamental base class to derive the test classes from. The `MyProjectWebTestBase` base class used above inherits from the `AbpAspNetCoreIntegratedTestBase`, so we indirectly inherited the `AbpAspNetCoreIntegratedTestBase`.
This package provides the `AbpWebApplicationFactoryIntegratedTest` as the fundamental base class to derive the test classes from. It's inherited from the [WebApplicationFactory](https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests) class provided by the ASP.NET Core.

### Base Properties

The `AbpAspNetCoreIntegratedTestBase` provides the following base properties those are used in the tests:

* `Server`: A `TestServer` instance that hosts the web application in tests.
* `Client`: An `HttpClient` instance that is configured to perform requests to the test server.
* `ServiceProvider`: The service provider that you can resolve services in case of need.

### Base Methods

`AbpAspNetCoreIntegratedTestBase` provides the following methods that you can override if you need to customize the test server:

* `ConfigureServices` can be overridden to register/replace services only for the derived test class.
* `CreateHostBuilder` can be used to customize building the `IHostBuilder`.
The `MyProjectWebTestBase` base class used above inherits from the `AbpWebApplicationFactoryIntegratedTest`, so we indirectly inherited the `AbpWebApplicationFactoryIntegratedTest`.

See Also

* [Integration tests in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests)
* [Overall / Server Side Testing](../../Testing.md)
19 changes: 3 additions & 16 deletions docs/zh-Hans/UI/AspNetCore/Testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,23 +198,10 @@ ABP框架不提供任何基础设施来测试JavaScript代码. 你可以使用

> Volo.Abp.AspNetCore.TestBase 已经安装在 `.Web.Tests` 项目中.

此包提供的`AbpAspNetCoreIntegratedTestBase`作为派生测试类的基类. 上面使用的`MyProjectWebTestBase`继承自`AbpAspNetCoreIntegratedTestBase`, 因此我们间接继承了`AbpAspNetCoreIntegratedTestBase`.

### 基本属性

`AbpAspNetCoreIntegratedTestBase` 提供了测试中使用的以下基本属性:

* `Server`: 在测试中托管web应用程序的`TestServer`实例.
* `Client`: 为执行对测试服务器的请求配置`HttpClient`实例.
* `ServiceProvider`: 可以在你需要时处理服务提供服务.

### 基本方法

`AbpAspNetCoreIntegratedTestBase` 提供了以下方法, 如果需要自定义测试服务器, 可以重写这些方法:

* `ConfigureServices` 仅为派生测试类注册/替换服务时可以重写使用.
* `CreateHostBuilder` 可用于自定义生成 `IHostBuilder`.
此包提供的`AbpWebApplicationFactoryIntegratedTest`作为派生测试类的基类. 它继承自ASP.NET Core提供的[WebApplicationFactory](https://learn.microsoft.com/zh-cn/aspnet/core/test/integration-tests)类。
上面使用的`MyProjectWebTestBase`继承自`AbpWebApplicationFactoryIntegratedTest`, 因此我们间接继承了`AbpWebApplicationFactoryIntegratedTest`.

另请参阅

* [ASP.NET Core 中的集成测试](https://learn.microsoft.com/zh-cn/aspnet/core/test/integration-tests)
* [总览/服务器端测试](../../Testing.md)
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="$(MicrosoftAspNetCorePackageVersion)" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace Volo.Abp.AspNetCore.TestBase;

[Obsolete("Use AbpWebApplicationFactoryIntegratedTest instead.")]
public class AbpAspNetCoreAsyncIntegratedTestBase<TModule>
where TModule : IAbpModule
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace Volo.Abp.AspNetCore.TestBase;
/// <typeparam name="TStartupModule">
/// Can be a module type or old-style ASP.NET Core Startup class.
/// </typeparam>
[Obsolete("Use AbpWebApplicationFactoryIntegratedTest instead.")]
public abstract class AbpAspNetCoreIntegratedTestBase<TStartupModule> : AbpTestBaseWithServiceProvider, IDisposable
where TStartupModule : class
{
Expand Down Expand Up @@ -51,7 +52,7 @@ protected virtual IHostBuilder CreateHostBuilder()
{
webBuilder.UseStartup<TStartupModule>();
}

webBuilder.UseAbpTestServer();
})
.UseAutofac()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Volo.Abp.AspNetCore.TestBase;

public abstract class AbpWebApplicationFactoryIntegratedTest<TProgram> : WebApplicationFactory<TProgram>
where TProgram : class
{
protected HttpClient Client { get; set; }

protected IServiceProvider ServiceProvider => Services;

protected AbpWebApplicationFactoryIntegratedTest()
{
Client = CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
ServiceProvider.GetRequiredService<ITestServerAccessor>().Server = Server;
}

protected override IHost CreateHost(IHostBuilder builder)
{
builder.ConfigureServices(ConfigureServices);
return base.CreateHost(builder);
}

protected virtual T? GetService<T>()
{
return Services.GetService<T>();
}

Check warning on line 37 in framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpWebApplicationFactoryIntegratedTest.cs

View check run for this annotation

Codecov / codecov/patch

framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpWebApplicationFactoryIntegratedTest.cs#L35-L37

Added lines #L35 - L37 were not covered by tests

protected virtual T GetRequiredService<T>() where T : notnull
{
return Services.GetRequiredService<T>();
}

protected virtual void ConfigureServices(IServiceCollection services)
{

}

#region GetUrl

/// <summary>
/// Gets default URL for given controller type.
/// </summary>
/// <typeparam name="TController">The type of the controller.</typeparam>
protected virtual string GetUrl<TController>()
{
return "/" + typeof(TController).Name.RemovePostFix("Controller", "AppService", "ApplicationService", "IntService", "IntegrationService", "Service");
}

/// <summary>
/// Gets default URL for given controller type's given action.
/// </summary>
/// <typeparam name="TController">The type of the controller.</typeparam>
protected virtual string GetUrl<TController>(string actionName)
{
return GetUrl<TController>() + "/" + actionName;
}

/// <summary>
/// Gets default URL for given controller type's given action with query string parameters (as anonymous object).
/// </summary>
/// <typeparam name="TController">The type of the controller.</typeparam>
protected virtual string GetUrl<TController>(string actionName, object queryStringParamsAsAnonymousObject)
{
var url = GetUrl<TController>(actionName);

Check warning on line 75 in framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpWebApplicationFactoryIntegratedTest.cs

View check run for this annotation

Codecov / codecov/patch

framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpWebApplicationFactoryIntegratedTest.cs#L74-L75

Added lines #L74 - L75 were not covered by tests

var dictionary = new RouteValueDictionary(queryStringParamsAsAnonymousObject);
if (dictionary.Any())
{
url += "?" + dictionary.Select(d => $"{d.Key}={d.Value}").JoinAsString("&");
}

Check warning on line 81 in framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpWebApplicationFactoryIntegratedTest.cs

View check run for this annotation

Codecov / codecov/patch

framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpWebApplicationFactoryIntegratedTest.cs#L77-L81

Added lines #L77 - L81 were not covered by tests

return url;
}

Check warning on line 84 in framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpWebApplicationFactoryIntegratedTest.cs

View check run for this annotation

Codecov / codecov/patch

framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpWebApplicationFactoryIntegratedTest.cs#L83-L84

Added lines #L83 - L84 were not covered by tests

#endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Volo.Abp.Modularity;

namespace Volo.Abp.AspNetCore.TestBase;

public static class WebApplicationBuilderExtensions
{
public async static Task RunAbpModuleAsync<TModule>(this WebApplicationBuilder builder, Action<AbpApplicationCreationOptions>? optionsAction = null)
where TModule : IAbpModule
{
var assemblyName = typeof(TModule).Assembly.GetName()?.Name;
if (!assemblyName.IsNullOrWhiteSpace())
{
// Set the application name as the assembly name of the module will automatically add assembly to the ApplicationParts of MVC application.
builder.Environment.ApplicationName = assemblyName!;
}
builder.Host.UseAutofac();
await builder.AddApplicationAsync<TModule>(optionsAction);
var app = builder.Build();
await app.InitializeApplicationAsync();
await app.RunAsync();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Builder;
using Volo.Abp.AspNetCore;
using Volo.Abp.AspNetCore.App;
using Volo.Abp.AspNetCore.TestBase;

var builder = WebApplication.CreateBuilder();
await builder.RunAbpModuleAsync<AppModule>();

public partial class Program
{
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

namespace Volo.Abp.AspNetCore.MultiTenancy;

public abstract class AspNetCoreMultiTenancyTestBase : AbpAspNetCoreTestBase<App.Startup>
public abstract class AspNetCoreMultiTenancyTestBase : AbpAspNetCoreTestBase<Program>
{

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,22 @@ public AspNetCoreMultiTenancy_WithDomainResolver_Tests()
_options = ServiceProvider.GetRequiredService<IOptions<AbpAspNetCoreMultiTenancyOptions>>().Value;
}

protected override IHostBuilder CreateHostBuilder()
protected override void ConfigureServices(IServiceCollection services)
{
return base.CreateHostBuilder().ConfigureServices(services =>
services.Configure<AbpDefaultTenantStoreOptions>(options =>
{
services.Configure<AbpDefaultTenantStoreOptions>(options =>
options.Tenants = new[]
{
options.Tenants = new[]
{
new TenantConfiguration(_testTenantId, _testTenantName)
};
});
new TenantConfiguration(_testTenantId, _testTenantName)
};
});

services.Configure<AbpTenantResolveOptions>(options =>
{
options.AddDomainTenantResolver("{0}.abp.io:8080");
});
services.Configure<AbpTenantResolveOptions>(options =>
{
options.AddDomainTenantResolver("{0}.abp.io:8080");
});

base.ConfigureServices(services);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,17 @@ public AspNetCoreMultiTenancy_Without_DomainResolver_Tests()
_options = ServiceProvider.GetRequiredService<IOptions<AbpAspNetCoreMultiTenancyOptions>>().Value;
}

protected override IHostBuilder CreateHostBuilder()
protected override void ConfigureServices(IServiceCollection services)
{
return base.CreateHostBuilder().ConfigureServices(services =>
services.Configure<AbpDefaultTenantStoreOptions>(options =>
{
services.Configure<AbpDefaultTenantStoreOptions>(options =>
options.Tenants = new[]
{
options.Tenants = new[]
{
new TenantConfiguration(_testTenantId, _testTenantName)
};
});
new TenantConfiguration(_testTenantId, _testTenantName)
};
});

base.ConfigureServices(services);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,6 @@
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace Volo.Abp.AspNetCore.Mvc;

public abstract class AspNetCoreMvcTestBase : AbpAspNetCoreTestBase<Startup>
public abstract class AspNetCoreMvcTestBase : AbpAspNetCoreTestBase<Program>
{
protected override IHostBuilder CreateHostBuilder()
{
var contentRootPath = CalculateContentRootPath(
"Volo.Abp.AspNetCore.Mvc.Tests.csproj",
string.Format(
"Volo{0}Abp{0}AspNetCore{0}App",
Path.DirectorySeparatorChar
)
);

return base.CreateHostBuilder()
.UseContentRoot(contentRootPath);
}

private static string CalculateContentRootPath(string projectFileName, string contentPath)
{
var currentDirectory = Directory.GetCurrentDirectory();
while (!ContainsFile(currentDirectory, projectFileName))
{
currentDirectory = new DirectoryInfo(currentDirectory).Parent.FullName;
}

return Path.Combine(currentDirectory, contentPath);
}

private static bool ContainsFile(string currentDirectory, string projectFileName)
{
return Directory
.GetFiles(currentDirectory, "*.*", SearchOption.TopDirectoryOnly)
.Any(f => Path.GetFileName(f) == projectFileName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@
_auditingStore = ServiceProvider.GetRequiredService<IAuditingStore>();
}

protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services)
protected override void ConfigureServices(IServiceCollection services)
{
_auditingStore = Substitute.For<IAuditingStore>();
services.Replace(ServiceDescriptor.Singleton(_auditingStore));
base.ConfigureServices(context, services);
base.ConfigureServices(services);
}

Check warning on line 30 in framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditIntegrationServiceTestController_Tests.cs

View check run for this annotation

Codecov / codecov/patch

framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditIntegrationServiceTestController_Tests.cs#L29-L30

Added lines #L29 - L30 were not covered by tests

[Fact]
public async Task Should_Write_Audit_Log_For_Controllers_With_IntegrationService_Attribute_If_IsEnabledForIntegrationServices()
Expand Down Expand Up @@ -55,4 +55,4 @@
await GetResponseAsync("/integration-api/audit-test/");
await _auditingStore.DidNotReceive().SaveAsync(Arg.Any<AuditLogInfo>());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ public AuditTestController_Tests()
_auditingStore = ServiceProvider.GetRequiredService<IAuditingStore>();
}

protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services)
protected override void ConfigureServices(IServiceCollection services)
{
_auditingStore = Substitute.For<IAuditingStore>();
services.Replace(ServiceDescriptor.Singleton(_auditingStore));
base.ConfigureServices(context, services);
base.ConfigureServices(services);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
_auditingStore = ServiceProvider.GetRequiredService<IAuditingStore>();
}

protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services)
protected override void ConfigureServices(IServiceCollection services)
{
_auditingStore = Substitute.For<IAuditingStore>();
services.Replace(ServiceDescriptor.Singleton(_auditingStore));
base.ConfigureServices(context, services);
base.ConfigureServices(services);

Check warning on line 29 in framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage_Tests.cs

View check run for this annotation

Codecov / codecov/patch

framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage_Tests.cs#L29

Added line #L29 was not covered by tests
}

[Fact]
Expand Down
Loading
Loading