Code Monkey home page Code Monkey logo

alba's Introduction

Alba

Discord Nuget Nuget

Tooling for better integration testing against ASP.Net Core applications. Check out the documentation for examples.

To work with the code, just open the solution at src/Alba.sln and go. No other setup necessary.

To run the documentation locally, use build docs on Windows or ./build.sh docs on Linux or OSX. The documentation website will require a recent installation of NPM. The documentation is built with VitePress.

Support Plans

JasperFx logo

While Alba is open source, JasperFx Software offers paid support and consulting contracts for Alba.

alba's People

Contributors

anth12 avatar dependabot[bot] avatar dgrozenok avatar gitter-badger avatar hawxy avatar jeremydmiller avatar joemcbride avatar jrios avatar lodewijksioen avatar mslot avatar mysticmind avatar oskardudycz avatar pondidum avatar rytmis avatar simoncropp avatar yuriylsh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

alba's Issues

PATCH Method not supported as entrance to UrlExpression builder

The following cannot be used:

return run(_ =>
{
    _.Patch.Url("/querystring2?test=value&foo=bar");
    _.Patch.Json(new {});
});

The following must instead be used:

return run(_ =>
{
    _.Configure = context => context.HttpMethod("PATCH");
    var urlExpression = (IUrlExpression)_
    _.urlExpression.Url("/querystring2?test=value&foo=bar");
    _.urlExpression.Json(new {});
});

Alba +Autofac

Hello,
I am trying to use Alba for Integration testing in my project which uses Autofac as DI container as described here -
But I get following error on this line -
using (var system = new SystemUnderTest(hostBuilder))
Cannot convert from IHostBuilder to IwebHostBuilder.

404s when using Api Versioning

If you setup your ASP.NET Core project with Api Versioning, Alba will give 404s for nearly all routes.

I tracked this down to the following line of code from this sample:

app.UseSwaggerUI(
  options =>
  {
      // build a swagger endpoint for each discovered API version
      foreach ( var description in provider.ApiVersionDescriptions )
      {
          options.SwaggerEndpoint( $"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant() );
      }
  } );

IApiVersionDescriptionProvider implementation appears to lazy-load the route/action information. When that code is run in Alba, it does appear to have the correct versioning information. However, doing a get request through Alba you get a 404. If you comment out the foreach then Alba works fine.

This seems to be some sort of timing issue. My only other guess is that the route versioning information is somehow not loaded properly when that class is initialized. The versioning appears to use an IStartupFilter to register route information, as shown here. Think that could possibly be related?

sealed class InjectApiVersionRoutePolicy : IStartupFilter
{
    readonly IApiVersionRoutePolicy routePolicy;

    public InjectApiVersionRoutePolicy( IApiVersionRoutePolicy routePolicy ) => this.routePolicy = routePolicy;

    public Action<IApplicationBuilder> Configure( Action<IApplicationBuilder> next )
    {
        Contract.Requires( next != null );
        Contract.Ensures( Contract.Result<Action<IApplicationBuilder>>() != null );

        return app =>
        {
            next( app );
            app.UseRouter( builder => builder.Routes.Add( routePolicy ) );
        };
    }
}

IStartupFilter's do appear to be called (I wrote a custom one to see that it did). Any ideas @jeremydmiller ?

Reconfigure ServiceCollection

What I'd like to do is inject some mocked classes after the ServiceCollection has been built.

Of course, I could do something like change the HostingEnvironment to know if the environment was called "test" or something to do something else but it feels less elegant.

Would you accept a PR to add an Action<IServiceCollection> or something to allow manipulation of the service collection before the service provider is built?

Lamar interaction differs between Debug and Release mode

This issue "moved" from the Lamar repo to here as it seems more likely this is a problem in Alba (or Xunit, I suppose.)

The problem occurs when an object is registered outside the Startup class in Alba, using .ConfigureServices, and a ServiceRegistrywithin the application makes use of that registered type to register another type. This works fine when running in Debug mode, but under Release, the second type registration is not found. Probably better explained with a test...

  • If you run this in Debug mode, the test passes, however, if run in Release, it fails.
  • If you remove the use of ServiceRegistries, and make the services.For... call in ConfigureContainer, it works in both Release and Debug
  • WhatDidIScan indicates that in Debug mode the root assembly is my testapp (LamarBug), but under Release is Microsoft.AspNetCore.Hosting (full output at the bottom of this issue)
  • If I use the Registry directly (services.IncludeRegistry<RestRegistry>();) the test passes

I have a workaround in place, so this isn't drastically urgent, but just caused a lot of head-scratching when I found it.

Package versions used:

<PackageReference Include="Alba.AspNetCore2" Version="1.4.3" />
<PackageReference Include="Lamar" Version="1.1.2" />
<PackageReference Include="Lamar.Microsoft.DependencyInjection" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.1.5" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />

I can upload the test project if that would help.

[Fact]
public Task<IScenarioResult> Test1()
{
	var storage = new InMemoryStorage();
	var system = SystemUnderTest.ForStartup<Startup>();

	system.Configure(builder => builder.UseLamar());
	system.ConfigureServices(services =>
	{
		services.AddSingleton<IStorage>(storage);
	});

	return system.Scenario(_ =>
	{
		_.Get.Url("/Toggles");
		_.StatusCodeShouldBe(HttpStatusCode.OK);
		_.ContentShouldBe("{\"message\":\"LamarBug.InMemorySession\"}");
	});
}

public interface IStorage
{
	IStorageSession CreateSession();
}

public interface IStorageSession
{
}

public class InMemoryStorage : IStorage
{
	public IStorageSession CreateSession() => new InMemorySession();
}

public class InMemorySession : IStorageSession
{
}

public class Startup
{
	public void ConfigureContainer(ServiceRegistry services)
	{
		services.AddMvc();

		services.Scan(_ =>
		{
			_.TheCallingAssembly();
			_.WithDefaultConventions();
			_.LookForRegistries();
		});
		
		//services.For<IStorageSession>().Use(c => c.GetInstance<IStorage>().CreateSession()).Scoped();
	}

	public void Configure(IApplicationBuilder app, IHostingEnvironment env)
	{
		app.UseMvc();
	}
}


[Route("Toggles")]
public class TogglesController : Controller
{
	private readonly IStorageSession _session;

	public TogglesController(IStorageSession session)
	{
		_session = session;
	}

	[Route("")]
	[HttpGet]
	public async Task<IActionResult> Get()
	{
		return new JsonResult(new { Message = _session.GetType().ToString() });
	}
}

public class RestRegistry : ServiceRegistry
{
	public RestRegistry()
	{
		Scan(a =>
		{
			a.TheCallingAssembly();
			a.WithDefaultConventions();
		});

		For<IStorageSession>().Use(c => c.GetInstance<IStorage>().CreateSession()).Scoped();
	}
}

Stack Trace:

System.InvalidOperationException : Unable to resolve service for type 'LamarBug.IStorageSession' while attempting to activate 'LamarBug.TogglesController'.
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
   at lambda_method(Closure , IServiceProvider , Object[] )
   at Microsoft.AspNetCore.Mvc.Controllers.ControllerActivatorProvider.<>c__DisplayClass4_0.<CreateActivator>b__0(ControllerContext controllerContext)
   at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.<>c__DisplayClass5_0.<CreateControllerFactory>g__CreateController|0(ControllerContext controllerContext)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
   at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
   at Alba.SystemUnderTestExtensions.Scenario(ISystemUnderTest system, Action`1 configure)
   at Alba.SystemUnderTestExtensions.Scenario(ISystemUnderTest system, Action`1 configure)
--- End of stack trace from previous location where exception was thrown ---

Output of WhatDidIScan()
Debug:

2018-10-22 21:17:54 [Verbose] All Scanners
================================================================

Assemblies
----------
* LamarBug, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

Conventions
--------
* Default I[Name]/[Name] registration convention
* Lamar.Scanning.Conventions.FindRegistriesScanner

Assemblies
----------
* LamarBug, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

Conventions
--------
* Default I[Name]/[Name] registration convention

No problems were encountered in exporting types from Assemblies

And Release

2018-10-22 21:17:02 [Verbose] All Scanners
================================================================

Assemblies
----------
* Microsoft.AspNetCore.Hosting, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60

Conventions
--------
* Default I[Name]/[Name] registration convention
* Lamar.Scanning.Conventions.FindRegistriesScanner

No problems were encountered in exporting types from Assemblies

Separate Alba.Mvc package

There are some specific things that become framework centric. Do the reverse url lookup and use the JsonSerializerSettings off of IOptions options for the JSON formatting.

Tests on HttpRequestBody

  • JsonInputIs should write content-type and content-length
  • TextIs should write content-type and content-length

Relative content root being overwritten

I'm currently configuring the TestServer by using the IWebHostBuilder delegate like so:

_client = SystemUnderTest.ForStartup<Startup>(x =>
{
    x.UseEnvironment("Development");
    x.UseSolutionRelativeContentRoot("src/Website");
    return x;
}).Server.CreateClient();

I want to set /src/Website as the ContentRoot (as opposed to the bin directory), however I'm noticing that the ContentRoot is then being overwritten by AppContext.BaseDirectory within here

It strikes me that the .useContentRoot(...) call on the aforementioned line should only occur if the content root hasn't already been set by the invocation of the builder delegate above it.

Target .NETFramework 4.5.1

Is it possible to target .NETFramework 4.5.1 as well? It looks like all dependencies are targeting it, so it shouldn't be a problem

Alba + Razor

It appears that to be able to invoke an endpoint that returns a Razor view you have to manually tell Razor where the needed Assemblies are. Yes, I just threw up a little myself. The end user may need to manually add additional assemblies.

Maybe this is the start of #28 ?

Original problem/solution: aspnet/Hosting#954 (comment)

public class SystemTestBase
{
    protected Task<IScenarioResult> run(Action<Scenario> configuration, Action<SystemUnderTest> systemConfigure = null)
    {
        using (var system = SystemUnderTest.ForStartup<Startup>())
        {
            system.ConfigureRazor<Startup>();

            system.Environment.EnvironmentName = "Local";
            systemConfigure?.Invoke(system);
            return system.Scenario(configuration);
        }
    }
}

public static class SystemUnderTestExtensions
{
    public static void ConfigureRazor<T>(this SystemUnderTest system, params string[] additionalAssemblies)
    {
        system.ConfigureServices(c =>
        {
            c.Configure((RazorViewEngineOptions options) =>
            {
                var previous = options.CompilationCallback;
                options.CompilationCallback = (context) =>
                {
                    previous?.Invoke(context);
                    context.Compilation = context.Compilation.AddReferences(GetReferences<T>(additionalAssemblies));
                };
            });
        });
    }

    private static IEnumerable<MetadataReference> GetReferences<T>(IEnumerable<string> additionalAssemblies)
    {
        var assemblyNames = new List<string>
        {
            "mscorlib",
            "System.Dynamic.Runtime",
            "System.Private.CoreLib",
            "System.Runtime",
            "Microsoft.AspNetCore.Html.Abstractions",
            "Microsoft.AspNetCore.Razor"
        };

        var assembly = typeof(T).GetTypeInfo().Assembly;
        var assemblies = assembly.GetReferencedAssemblies().Select(x => MetadataReference.CreateFromFile(Assembly.Load(x).Location))
                .ToList();

            assemblies.AddRange(CreateReferences(additionalAssemblies.Concat(assemblyNames).ToArray()));

        return assemblies;
    }

    private static IEnumerable<PortableExecutableReference> CreateReferences(params string[] assemblies)
    {
        return assemblies.Select(x =>
        {
            try
            {
                return MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName(x)).Location);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                return null;
            }
        }).Where(x => x != null);
    }
}

Make application's level IServiceProvider available in classes implementing IScenarioAssertion

In some cases there is a need to resolve a service from within the class implementing the IScenarionAssertion interface.

Ex:

public class SuccessResultAssertion : GraphQLAssertion
    {
        private readonly string _result;

        public SuccessResultAssertion(string result) => _result = result;

        public override void Assert(Scenario scenario, HttpContext context, ScenarioAssertionException ex)
        {
            var writer = (IDocumentWriter) context.RequestServices.GetService(typeof(IDocumentWriter));
            var expectedResult = writer.Write(CreateQueryResult(_result));

            var body = ex.ReadBody(context);
            if (!body.Equals(expectedResult))
            {
                ex.Add($"Expected '{expectedResult}' but got '{body}'");
            }
        }
    }

Theoritecally we could use passed in HttpContext to resolve the service like in the example but it happens that sometimes passed in context.RequestServices is null

Maybe it would help either exposing Services on the scenario passed it, or by another means ?

If I run a series of test, for the first test I can resolve service from context.RequestServices but for all next runing services context.RequestServices will be always null. I'm still not sure why this happens as SystemUnderTests is disposed each time a test runs.

What do you think?

.NET core 2.1 support

Is support for .NET core 2.1 planned?

I just wonder whether I have to rewrite my integration tests using Microsoft.AspNetCore.Mvc.Testing.

PS
I took a crack at debugging but it exceeded my skills. I fear that changes in .net core 2,1 are too great to easily migrate Alba.

Capture request and replay

Would love to have the ability to easily capture a full request and then use that file to replay the request in a test using this lib.

HttpContext.Request.QueryString does not support passing arrays as parameters

Currently unable to send an array of items on a query string.
Calling _.Get.Url("/endpoint").QueryString("relatedCustomerIds", 123).QueryString("relatedCustomerIds", 456) causes only relatedCustomerIds=456 to be appended to the resultant url. This problem could be solved by allowing multiple values per-key, or simply not manipulating the url to split query parameters into the HttpContext.Request.QueryString dictionary.

public static void RelativeUrl(this HttpContext context, string relativeUrl)

^is the where the change would need to be made.

IsAjaxRequest

Might still be helpful. From fubu:

    public static class AjaxExtensions
    {
        public const string XmlHttpRequestValue = "XMLHttpRequest";
        public const string XRequestedWithHeader = "X-Requested-With";

        /// <summary>
        /// Tries to determine whether or not a given request is an Ajax request by looking for the "X-Requested-With" header
        /// </summary>
        /// <param name="requestInput"></param>
        /// <returns></returns>
        public static bool IsAjaxRequest(this IDictionary<string, object> requestInput)
        {
            object value;
            return
                requestInput.TryGetValue(XRequestedWithHeader, out value)
                && IsAjaxRequest(value);
        }

        /// <summary>
        /// Tries to determine whether or not a given request is an Ajax request by looking for the X-Requested-With" header
        /// </summary>
        /// <param name="requestInput"></param>
        /// <returns></returns>
        public static bool IsAjaxRequest(this IRequestData requestInput)
        {
            bool result = false;
            requestInput.Value(XRequestedWithHeader, value => result = IsAjaxRequest(value.RawValue));
            return result;
        }

        /// <summary>
        /// Determines whether or not a request is an Ajax request by comparing the value to "XMLHttpRequest"
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private static bool IsAjaxRequest(this object value)
        {
            return XmlHttpRequestValue.Equals(value as string, StringComparison.InvariantCultureIgnoreCase);
        }

        /// <summary>
        /// Determines whether or not a request is an Ajax request by searching for a value of the "X-Requested-With" header
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public static bool IsAjaxRequest(this IBindingContext context)
        {
            bool returnValue = false;
            context.Data.ValueAs<object>(XRequestedWithHeader, val => returnValue = val.IsAjaxRequest());
            return returnValue;
        }

        /// <summary>
        /// Determines whether or not a request is an Ajax request by searching for a value of the "X-Requested-With" header
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public static bool IsAjaxRequest(this IHttpRequest request)
        {
            var headers = request.GetHeader(XRequestedWithHeader);
            return headers.Any(x => x.EqualsIgnoreCase(XmlHttpRequestValue));
        }
    }

Example: uploading FormFile

Hi, is it possible that you can provide an example of a scenario with IFormFile uploads using a Post request?

I am trying to attached as formdata, and setting "content-type", "multipart/form-data", however nothing seems to work. I.e.
_.Post.FormData(form).ToUrl(_basePath);
where form is MultipartFormDataContent containing the file

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.