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

Add support for additional types, specified by user. #5

Closed
JacobHeidelbach opened this issue Apr 4, 2013 · 13 comments
Closed

Add support for additional types, specified by user. #5

JacobHeidelbach opened this issue Apr 4, 2013 · 13 comments

Comments

@JacobHeidelbach
Copy link

A nice addition, would be to allow the user to add custom converters to the type-conversion.
This would give the flexibility of the Dynamic but with the freedom of creating custom types whenever this is needed.
At the moment, only the types specified below is supported.

One could utilize the IObjectContainer to register additional converters, and this could be queried in the process below.

 private static object CreateTypedValue(string valueFromTable)
        {
            // TODO: More types here?
            int i;
            if (int.TryParse(valueFromTable, out i))
                return i;

            double d;
            if (Double.TryParse(valueFromTable, out d))
                return d;

            bool b;
            if (Boolean.TryParse(valueFromTable, out b))
                return b;

            DateTime dt;
            if (DateTime.TryParse(valueFromTable, out dt))
                return dt;

            return valueFromTable;
        }
@vzakanj
Copy link

vzakanj commented Apr 8, 2016

This would indeed be useful for situations where I don't want the conversion to occur (e.g. want to treat the values from the tables as strings).
Another useful addition would be to explicitly specify culture or format for the date parsing.

I could chip in on this, just let me know if there are any special requirements for building the project.

@Lumirris
Copy link

Lumirris commented Apr 8, 2016

@vzakanj I submitted PR #4 a while back that seems to relate to your comment - would generics fit the bill? I wanted to do exactly what you suggest as an example, treating values from tables as strings, even if they looked like another type. If you want to use any of that as a starting point for your own PR, please do.

@marcusoftnet
Copy link
Owner

Hi,

that sounds like a good idea! The PR in there now is pretty big (43 files touched and changed), can you use that as your base and make a smaller, more directed PR?

Looking forward to something new in this old library. Happy that you are using it.

@vzakanj
Copy link

vzakanj commented Apr 9, 2016

@Lumirris @marcusoftnet generics wouldn't really be fitting for my use case because I'm currently writing specs for logic which reads a CSVfile, processes it and writes it back to another csv file. This process is generic meaning the CSV fields are detected on the fly, an ExpandoObject is created with properties which match the names of the CSV columns and it's processed further down the line to produce the resulting CSV.

This means that I don't have a strongly typed class in this case, and would have to create a class which would correspond to the result of a single CSV processing - meaning I'd the number of these classes would match the number of different CSV entries. That being said I'd also either need different step definitions for each of these (each one calling the method with a different type) or use reflection to resolve the type param from a Specflow parameter and dynamically invoke the method with that type param.

What would work better in my case is to either go with the solution @Friischling suggested (would need to get more acquainted with specflow code/structure to see how that would work - from what I can gather it boils down to registering converters with a DI container and then using them to convert data, otherwise fallback to default behavior), or have an overloads for CompareToDynamicSet/CompareToDynamicInstance/CreateDynamicSet/CreateDynamicInstance which takes in a collection of converters as an additional parameter.

Any feedback on this would be great.

Also, a few questions regarding the project:

  1. How come the packages directory is versioned?
  2. I see that this is a VS 2010 solution, would it be viable to convert it to a VS 2015 solution?

@marcusoftnet
Copy link
Owner

Ok - I've read the comment now and I would suggest that you go with the DependencyInjection to register a new converter. Constructor DI is supported by SpecFlow out of the box so that would be pretty easy.

However that is in the step definition class. The helper functions are defined on static class as extension methods, to show up nicely on the SpecFlow Table object. Getting that injected dependency to be visible and usable on the helper functions can be tricky, me thinks.

One way could be to simple expose an property where you set the "converter" object in the step definitions. If nothing is set (i.e. the Converter property is null) we'll use my default conversion.

I'm all for converting to 2015 (haven't touched this code in ages) but let's create a separate PR for that.

@marcusoftnet
Copy link
Owner

Couldn't resist - fixed the VS2015 issue.

@vzakanj
Copy link

vzakanj commented Apr 11, 2016

Great, thanks!

@vzakanj
Copy link

vzakanj commented Apr 11, 2016

I looked at the implementation and it seems that method injection (passing the converters as an additional parameter) would fit most naturally here and here's why:

  • since it's a static [extension] method, as you've mentioned previously, I can only get a reference to the IObjectContainer through a static property (e.g. ScenarioContext.Current.ScenarioContainer), but this will not work when running multiple tests in parallel (i.e. multithreading)
  • not sure what you meant by exposing a property, but since C# doesn't support extension properties, this could be done by an extension method - e.g. SetConverters(IEnumerable converters) with a static backing field to hold the converters, but this will still be a problem in multithreading scenarios

So my suggestion would be to have an overload for the existing extension methods which can take an IEnumerable (these would be ordered by conversion priority) or a single IValueConverter. Another option is to also add overloads with Func<string, object> so that the conversion logic can be specified inline for simple cases (i.e. no need for a separate class).

So, method injection seems like the best fit given the current API, but let me know if you have any better ideas.

@marcusoftnet
Copy link
Owner

Oh - me stupid. Been in JavaScript land for too long. No extension properties. Forgot about that.

The solution sounds good - go for it!

@anniekvandijk
Copy link

Hi, I'm having problems with the conversion of my table. I need to have only strings to compare this with a json message.

Is it possible to override the CreateTypedValue method? Or do you have the alternative with CreateDynamicInstance ready?

@marcusoftnet
Copy link
Owner

Hi @anniekvandijk - not entirely sure what you are looking for, but you can do quite a lot on your own with SpecFlow, even without the use of my library.

Check out step argument conversion that is a very power feature that has helped me a lot.

With it you can write stuff like:

Given this JSON string '{}'

and then write a generic conversion for all JSON string with:

[Binding]
public class Transforms
{
    [StepArgumentTransformation("JSON string '{}'"]
    public IList<object> JSONTransform(string json)
    {
       // Do some funky transformation of the json parameter
      // into an object structure (using, for example http://www.newtonsoft.com/json) 
    }
}

I hope this helps you somewhat.

Merry Christmas

@icnocop
Copy link

icnocop commented Nov 6, 2018

I created PR #21 which allows the user to optionally specify a custom property value parser.

If not specified, the default property value parser will be used.

For example:

    public class CustomPropertyValueParser : IPropertyValueParser
    {
        public object Parse(string value)
        {
            return 42;
        }
    }

It can then be used as follows:

            Options options = new Options
            {
                PropertyValueParser = new CustomPropertyValueParser()
            };
            dynamic instance = table.CreateDynamicInstance(options);

Thank you.

@marcusoftnet
Copy link
Owner

Closing this - too long ago...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants