How to map a Map of enum and String in Cucumber 7 - cucumber

I am using cucumber 7 and I have the following Then statement in my step definition file:
#Then("^with the following Properties:$")
public void with_the_following_Properties(Map<Gender, String> properties) {
}
This gives me the following exception:
io.cucumber.core.exception.CucumberException: Could not convert arguments for step [^with the following properties:$] defined at 'com.test.glue.TestStepDefs.with_the_following_Properties(java.util.Map<com.test.Gender, java.lang.String>)'.
It appears you did not register a data table type.
at io.cucumber.core.runner.PickleStepDefinitionMatch.registerDataTableTypeInConfiguration(PickleStepDefinitionMatch.java:96)
It seems only Map<String, String> is allowed.
Any suggestion(s).

Cucumber doesn't know how to turn strings into enums. So as the exception message explains you have to register a data table type:
public class StepDefinitions {
#DataTableType
public Gender authorEntryTransformer(String entry) {
return Gender.valueOf(entry);
}
}

I found a simple solution as below :
#Then("^with the following Properties:$")
public void with_the_following_Properties(Map<String, String> properties) {
Map<Gender, String> map = new HashMap<>();
properties.forEach((k, v) -> map.put(Gender.valueOf(k), v));
}

Related

Map object properties to dictionary on automapper

I am trying to map an object to a dictionary in a way that each property will be a dictionary item.
Object with id and name -> dictionary with two items containing property name and value.
I know it is a simple thing, but I was not able to find a solution for it. Maybe it is something I am not understanding...
I receive the following error:
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters
=======================================================================================================================================================================================================================================================================
Book -> Dictionary`2 (Destination member list)
Book -> System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.String, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]] (Destination member list)
Unmapped properties:
Keys
Values
Item
In the following code you can see my implementation:
using AutoMapper;
var book = new Book()
{
Id = 1,
Name = "A"
};
IMapper mapper = new Mapper(new MapperConfiguration(
config => config.CreateMap<Book, Dictionary<string, string>>()
.ConstructUsing((source, dest) => new Dictionary<string, string>()
{
{ "id", source.Id.ToString() },
{"name", source.Name}
})));
mapper.ConfigurationProvider.AssertConfigurationIsValid();
var bookData = new Dictionary<string, string>();
mapper.Map(book, bookData);
public class Book
{
public int Id { get; set; }
public string Name { get; set; }
}
As #Lucian mentioned in a comment, Automapper has built-in methods to convert object from/to dynamic, Dictionary<string, object>.
However, I think that there is an issue if you implement a converter to Dictionary<string, string>.
Hence, it is better to build a custom implementation for the conversion as below:
using System.Reflection;
using Newtonsoft.Json;
public static class DictionaryExtensions
{
public static Dictionary<string, string> ToDictionary<T>(this T src) where T : new()
{
Dictionary<string, string> result = new ();
PropertyInfo[] properties = typeof(T).GetProperties();
foreach (PropertyInfo prop in properties)
{
result.Add(prop.Name, prop.PropertyType == typeof(string)
? prop.GetValue(src)?.ToString()
: JsonConvert.SerializeObject(prop.GetValue(src)));
}
return result;
}
}
For caller:
var bookData = book.ToDictionary();
Demo # .NET Fiddle

Cucumber converting TypeRegistryConfiguration to ParameterType annotation JAVA

Since TypeRegistry io.cucumber.core.api.TypeRegistry is #Deprecated I have troubles declaring my parameter annotations, I have now idea how to transform them to the #ParameterType
I tried this
#ParameterType(value = ".*", name = "foo")
public String foo(String foo) {
return "foobar";
}
#ParameterType("foo")
#When("I type {foo}")
public void iType(String string) {
System.out.println(string);
}
the step recognises this parameter and it compiles but I get following error:
io.cucumber.java.InvalidMethodSignatureException: A #ParameterType annotated method must have one of these signatures:
* public Author parameterName(String all)
* public Author parameterName(String captureGroup1, String captureGroup2, ...ect )
* public Author parameterName(String... captureGroups)
at com.dsm.steps.RequestAccessSteps.withTheInformationIconContaining(java.lang.String)
Note: Author is an example of the class you want to convert captureGroups to
but honestly I dont understand what they are trying to say
The fault is I put the #Parametertype("foo") above the step but thats not necessary and thus throwing the error. It works perfectly fine otherwise.
So this works:
public String foo(String foo) {
return "foobar";
}
#When("I type {foo}")
public void iType(String string) {
System.out.println(string);
}

Forgetting to map classes with AutoMapper

The application I'm working on has several places where we use AutoMapper to map entities.
The problem is if I had a model entity from one side to the other of the project, many times I forget to add the mapping for the new entity (I just need a copy paste from other entities), ending up that the solution compiles and I get no exception.
It just launches the application without full functionality and no debugging messages, which makes difficult to figure out what I've missed.
Is there any way to force the compiler at compile time to give me an error in case I forget to do a mapping?
AFAIK, there isn't a possibility to force compile-time checking for Automapper.
Nevertheless, there is a possibility to verify the correctness of your mappings:
After you've defined all your mappings, call the AssertConfigurationIsValid method which will throws an AutoMapperConfigurationException exception if the defined mappings are broken.
You can make this a part of your unit or integration test suite.
I had the same problem and decided to solve it by wrapping up AutoMapper. For each source-destination map I provide a method that I create after I've added it to my AutoMapper profile.
This may take away some of the ease of implementing AutoMapper but I find the compile time checking worth it.
public class MyType {
public string SomeProperty { get;set; }
}
public class MyOtherType {
public string SomeProperty { get;set; }
}
public class MyAlternateType {
public string AlternateProperty {get;set;}
}
public class AutoMapperProfile : Profile {
public AutoMapperProfile() {
CreateMap<MyType, MyOtherType>();
CreateMap<MyAlternateType, MyOtherType>()
.ForMember(ot => ot.SomeProperty, options => options.MapFrom(at => at.AlternateProperty));
}
}
public interface IMyMappingProvider {
// Uncomment below for Queryable Extensions
//IQueryable<TDestination> ProjectTo<TSource, TDestination>(IQueryable<TSource> source, params Expression<Func<TDestination, object>>[] membersToExpand);
//IQueryable<TDestination> ProjectTo<TSource, TDestination>(IQueryable<TSource> source, IDictionary<string, object> parameters, params string[] membersToExpand);
/*
* Add your mapping declarations below
*/
MyOtherType MapToMyOtherType(MyType source);
MyOtherType MapToMyOtherType(MyAlternateType source);
}
public class MyMappingProvider : IMyMappingProvider {
private IMapper Mapper { get; set; }
public MyMappingProvider(IMapper mapper) {
Mapper = mapper;
}
/* Uncomment this for Queryable Extensions
public IQueryable<TDestination> ProjectTo<TSource, TDestination>(IQueryable<TSource> source, params Expression<Func<TDestination, object>>[] membersToExpand) {
return new ProjectionExpression(source, Mapper.ConfigurationProvider.ExpressionBuilder).To<TDestination>(null, membersToExpand);
}
public IQueryable<TDestination> ProjectTo<TSource, TDestination>(IQueryable<TSource> source, IDictionary<string, object> parameters, params string[] membersToExpand) {
return new ProjectionExpression(source, Mapper.ConfigurationProvider.ExpressionBuilder).To<TDestination>(parameters, membersToExpand);
}
*/
/*
* Implement your mapping methods below
*/
public MyOtherType MapToMyOtherType(MyType source) {
return Mapper.Map<MyType, MyOtherType>(source);
}
public MyOtherType MapToMyOtherType(MyAlternateType source) {
return Mapper.Map<MyAlternateType, MyOtherType>(source);
}
}
If you are using the AutoMapper's Queryable extensions you can add the following class and uncomment the Queryable Extensions code above.
public static class QueryableExtensions {
/*
* Implement your extension methods below
*/
public static IQueryable<MyOtherType> ProjectToMyOtherType(this IQueryable<MyType> source, IMyMappingProvider mapper, params Expression<Func<MyOtherType, object>>[] membersToExpand)
{
return mapper.ProjectTo<MyType, MyOtherType>(source, membersToExpand);
}
public static IQueryable<MyOtherType> ProjectToMyOtherType(this IQueryable<MyAlternateType> source, IMyMappingProvider mapper, params Expression<Func<MyOtherType, object>>[] membersToExpand)
{
return mapper.ProjectTo<MyAlternateType, MyOtherType>(source, membersToExpand);
}
}
Tested with AutoMapper 6.1.1 using LinqPad:
var autoMapperConfig = new MapperConfiguration(cfg => { cfg.AddProfile(new AutoMapperProfile()); });
IMyMappingProvider mapper = new MyMappingProvider(autoMapperConfig.CreateMapper());
var myTypes = new List<MyType>()
{
new MyType() {SomeProperty = "Test1"},
new MyType() {SomeProperty = "Test2"},
new MyType() {SomeProperty = "Test3"}
};
myTypes.AsQueryable().ProjectToMyOtherType(mapper).Dump();
var myAlternateTypes = new List<MyAlternateType>()
{
new MyAlternateType() {AlternateProperty = "AlternateTest1"},
new MyAlternateType() {AlternateProperty = "AlternateTest2"},
new MyAlternateType() {AlternateProperty = "AlternateTest3"}
};
myAlternateTypes.AsQueryable().ProjectToMyOtherType(mapper).Dump();
mapper.MapToMyOtherType(myTypes[0]).Dump();
As #serge.karalenka said, don't forget to still test your mapping configuration by calling AssertConfigurationIsValid().

AutoMapper cannot convert enum to nullable int?

I got AutoMapperMappingException exception
Exception of type 'AutoMapper.AutoMapperMappingException' was thrown. ---> System.InvalidCastException: Invalid cast from 'DummyTypes' to 'System.Nullable`1[[System.Int32, ...
when
public enum DummyTypes : int
{
Foo = 1,
Bar = 2
}
public class DummySource
{
public DummyTypes Dummy { get; set; }
}
public class DummyDestination
{
public int? Dummy { get; set; }
}
[TestMethod]
public void MapDummy()
{
Mapper.CreateMap<DummySource, DummyDestination>();
Mapper.AssertConfigurationIsValid();
DummySource src = new DummySource()
{
Dummy = DummyTypes.Bar
};
Mapper.Map<DummySource, DummyDestination>(src);
}
Should not AutoMapper map this implicitly without any extra explicit rule?
P.S. I cannot change the definition of DummyDestination.Dummy to enum. I have to deal with such interfaces.
It looks like no, it won't take care of this automatically for you. Interestingly, it will map an enum to a regular int.
Looking at AutoMapper's source, I think the problematic line is:
Convert.ChangeType(context.SourceValue, context.DestinationType, null);
Assuming context.SourceValue = DummyTypes.Foo and context.DestinationType is int?, you would end up with:
Convert.ChangeType(DummyTypes.Foo, typeof(int?), null)
which throws a similar exception:
Invalid cast from 'UserQuery+DummyTypes' to
'System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0
So I think really the question is why can't we cast a variable of type enum to int? That question has already been asked here.
This seems like a bug in AutoMapper. Anyway the workaround is to map the property manually:
Mapper.CreateMap<DummySource, DummyDestination>()
.ForMember(dest => dest.Dummy, opt => opt.MapFrom(src => (int?)src.Dummy));
Just in case if anyone want to try using a type converter
Mapper.CreateMap<int?, DummyTypes.Foo?>().ConvertUsing(new FooTypeConverter());
public class FooTypeConverter: TypeConverter<int?, DummyTypes.Foo?>
{
protected override DummyTypes.Foo? ConvertCore(int? source)
{
return source.HasValue ? (DummyTypes.Foo?)source.Value : null;
}
}
Cheers

Adding new dynamic properties

we read in msdn we "Adding new dynamic properties" by using DynamicObject Class
i write a following program
public class DemoDynamicObject : DynamicObject
{
}
class Program
{
public static void Main()
{
dynamic dd = new DemoDynamicObject();
dd.FirstName = "abc";
}
}
But when i run this program it gives runtime error :'DemoDynamicObject' does not contain a definition for 'FirstName'
if we adding dynamic property by using DynamicObject Class then why it can give this error
can anyone tell me reason and solution?
When using DynamicObject as your base class, you should provide specific overrides to TryGetMember and TrySetMember to keep track of the dynamic properties you are creating (based on the DynamicObject MSDN documentation):
class DemoDynamicObject: DynamicObject
{
Dictionary<string, object> dictionary
= new Dictionary<string, object>();
public override bool TryGetMember(
GetMemberBinder binder, out object result)
{
string name = binder.Name;
return dictionary.TryGetValue(name, out result);
}
public override bool TrySetMember(
SetMemberBinder binder, object value)
{
dictionary[binder.Name] = value;
return true;
}
}
If you just want to have a dynamic object that you can add properties to, you can simply use an ExpandoObject instance, and skip the custom class inheriting from DynamicObject.

Resources