将可模拟的Spring依赖项注入到映射器定义中

时间:2020-07-31 07:47:48

标签: spring dependency-injection mocking mapstruct

我需要从自定义映射方法访问Spring bean。但是在单元测试该映射方法时,我还需要能够注入该Spring bean的模拟。

这是我的映射器类的一个最小示例:

@Mapper(componentModel = "spring")
public abstract class MyMapper {
    private final MyBean myBean;
    
    public MyMapper(MyBean myBean) {
        this.myBean = myBean;
    }

    @BeforeMapping
    protected MyElement initElement(MyElementDto dto) {
        // custom logic using the injected MyBean to initialize MyElement
    }
    
    public abstract MyElement map(MyElementDto dto);
}

我希望MapStruct生成一个使用MyMapper的参数化构造函数的实现,如下所示:

@Component
public class MyMapperImpl extends MyMapper {
    @Autowired
    public MyMapperImpl(MyBean myBean) {
        super(myBean);
    }

    // ... mapping function implementation ...
}

但是,MapStruct似乎忽略了参数化的构造函数,仅支持默认的无参数构造函数。

所以问题是:如何才能以最干净的方式实现这种逻辑,以便生成的mapper实现可以进行单元测试,并且可以正确地模拟MyBean依赖关系? strong>

使用MapStruct 1.3.0.Final,Spring 4.3.25.Release,Mockito 1.9.5和Junit 4.12。

1 个答案:

答案 0 :(得分:1)

解决方案

通过使用@ObjectFactory而不是@BeforeMapping进行元素初始化,找到了针对这种情况的解决方案。这样还可以改善代码结构,关注点分离和可测试性。

元素初始化逻辑将在单独的Spring bean中定义:

@Component
public class MyFactory {
    private final MyBean myBean;

    @Autowired
    public MyFactory(MyBean myBean) {
        this.myBean = myBean;
    }

    @ObjectFactory
    public MyElement initialize(MyElementDto dto) {
        // custom logic using the injected MyBean to initialize MyElement
    }
}

上述组件可以单独测试,可以模拟和注入MyBean

然后,MyMapper代码变得很短,里面没有自定义逻辑。甚至可以使用interface代替abstract class(尽管两者都可以很好地工作):

@Mapper(componentModel = "spring",
        uses = MyFactory.class,
        injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface MyMapper {
    MyElement map(MyElementDto dto);
}

生成的代码

生成的实现如下所示。首先,调用factory方法初始化目标对象,然后在工厂方法返回的目标对象上进行字段映射:

/** THIS IS AUTOMATICALLY GENERATED CODE **/
@Component
public class MyMapperImpl implements MyMapper {
    private final MyFactory myFactory;

    @Autowired
    public MyMapperImpl(MyFactory myFactory) {
        this.myFactory = myFactory;
    }

    @Override
    public MyElement map(MyElementDto dto) {
        if ( dto == null ) {
            return null;
        }

        MyElement myElement = myFactory.initialize( dto ); // <-- FACTORY USED HERE

        // ... field mapping code here, after initialization ...

        return myElement;
    }
}

单元测试

通过模拟和注入MyFactory可以轻松地对映射器进行单元测试。我想避免加载任何Spring上下文,因此我手动初始化了MyFactoryImpl实例。

@RunWith(MockitoJUnitRunner.class)
public class MyMapperTest {
    @Mock
    private MyFactory myFactory;
    
    private MyMapper myMapper;
    
    @Before
    public void setUp() {
        // ... myFactory stubs ...
        
        myMapper = new MyMapperImpl(myFactory);
    }
    
    // ... tests ...
}