【问题标题】:Mockable spring dependency injection into mapper definition可模拟的弹簧依赖注入映射器定义
【发布时间】:2020-11-21 01:32:16
【问题描述】:

我需要通过自定义映射方法访问 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 似乎忽略了参数化构造函数,只支持默认的无参数构造函数。

所以问题是:我怎样才能以最简洁的方式实现这种类型的逻辑,以便生成的映射器实现是可单元测试的,并且可以正确地模拟 MyBean 依赖项?

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

【问题讨论】:

标签: spring dependency-injection mocking mapstruct


【解决方案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);
}

生成的代码

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

/** 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 ...
}

【讨论】:

  • 也可以使用setter注入
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-03-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-17
  • 2014-05-19
  • 1970-01-01
  • 2011-05-10
相关资源
最近更新 更多