在我的应用中,所有域类都遵循标准化:
IEntity
Id
属性为protected
* IList
的属性在构造函数中受到保护和初始化。 以下是域实体的典型示例:
public class CheckListItemTemplate : IEntity
{
public virtual int Id { get; protected set; }
public virtual string Text { get; set; }
public virtual CheckListItemTemplate Parent { get; set; }
public virtual IList<CheckListItemTemplate> Itens { get; protected set; }
public CheckListItemTemplate()
{
Itens = new List<CheckListItemTemplate>();
}
public void AddItem(CheckListItemTemplate item)
{
item.Parent = this;
Itens.Add(item);
}
}
*这是因为id是由数据库生成的,并没有冒一些开发人员尝试设置此属性的风险。
我们在测试中使用了一个假的通用存储库:
public class Repository<T> : IRepository<T>
where T : class, IEntity
{
private readonly IDictionary<int, T> _context = new Dictionary<int, T>();
public void Delete(T obj)
{
_context.Remove(obj.Id);
}
public void Store(T obj)
{
if (obj.Id > 0)
_context[obj.Id] = obj;
else
{
var generateId = _context.Values.Any() ? _context.Values.Max(p => p.Id) + 1 : 1;
var stub = Mock.Get<T>(obj);
stub.Setup(s => s.Id).Returns(generateId);
_context.Add(generateId, stub.Object);
}
}
// ..
}
正如您在Store
*中看到的,所有测试对象(类型IEntity
)都应该是Mock
**。这是因为在UI项目中,当我们保存对象NHibernate时更新属性Id
。在测试项目中,我们必须手动执行此操作,并且我们无法使用新值设置属性Id
,因此解决方案是模拟Get
属性Id
的整个对象对应新的Id。这一行究竟是什么stub.Setup(s => s.Id).Returns(generateId)
。
*按照惯例,Id&lt; = 0的对象是新的,Id&gt; 0是数据库中的现有对象 **对于模拟我使用Moq。
Id
为受保护的最大的问题是因为Id
属性和protected
这一事实。
当我们谈论设计师时,这是一个很好的方法,但是当我们测试我们的应用程序时,这会带来巨大的不便。
例如,在我正在编写的测试中,我需要我的Fake存储库,其中已经填充了一些数据。
跟我来。我有以下课程(上面显示的是{CheckListItemTemplate
。)
public class Passo : IEntity
{
public int Id { get; protected set; }
public virtual IList<CheckListItemTemplate> CheckListItens { get; protected set; }
}
public class Processo : IEntity
{
public virtual int Id { get; protected set; }
public virtual Passo Passo { get; set; }
public virtual IList<CheckListItem> CheckListItens { get; protected set; }
}
保存Processo
后,第一个Passo
与Processo
相关联:(按字段Ordem
后的CreateAt
字段排序)
model.Passo = PassoRepositorio.All().OrderBy(p => p.Ordem).ThenBy(p => p.CreateAt).First();
model.CheckListItens.Clear();
Parallel.ForEach(Mapper.Map<IList<CheckListItem>>(model.Passo.CheckListItens), (it) => model.AddCheckListItem(it));
只要您保存新的Processo
,此代码就会运行。对于任何创建新Processo
的测试,此代码将被执行!
如果我们必须创建一个创建新Processo
的测试,我们的第一个目标是使用PassoRepositorio
和{{1}填充Passos
存储库中的一些虚拟数据* }专门针对上面的代码不会失败**。
*要使用虚拟数据填充对象,我使用AutoFixture **如果在
CheckListItemTemplates
存储库中找不到Passo
并且此.First()
没有核对清单Passo
,则会失败。
因此,我们需要一个Mapper.Map(model.Passo.CheckListItens)
和每个Passos
的存储库,其中包含Passo
列表。
请记住,每个对象CheckListItens
都应该是IEntity
,因此我们可以模拟属性Mock<>
首先配置我的Id
以使用一些虚拟数据填充我的存储库:
TestInitialize
然后我可以运行测试:
var fix = new Fixture();
var listPassos = fix.Build<Mock<Passo>>()
.Do((passo) => {
passo.SetupProperty(x => x.Nome, fix.Create<string>());
passo.SetupGet(x => x.CheckListItens).Returns(
fix.Build<CheckListItemTemplate>() // Needs to a Mock<>
.With(p => p.Texto)
.OmitAutoProperties()
.CreateMany(5).ToList()
);
})
.OmitAutoProperties()
.CreateMany(10);
foreach (var item in listPassos)
passoRepository.Store(item.Object);
我们创建了一个包含10个[TestMethod]
public void Salvar_novo_processo_modificar_data_atendimento_passo_atual()
{
// Arrange
var fix = new Fixture();
var vm = fix.Create<ProcessoViewModel>();
//Act
Controller.salvar(vm); // Problem here. (For convert ProcessoViewModel to Processo I use a AutoMaper. In repository needs destination to be a Mock<Processo>
var processo = Repository.Get(p => p.DataEntrada == vm.DataEntrada && p.ProximoAtendimento == vm.ProximoAtendimento);
//Asserts
processo.Should().NotBeNull();
processo.Passo.Should().NotBeNull();
}
的列表,其中每个Passo
实际上都是Passo
,太棒了!但是:
每个Mock<>
都有一个包含5个“模拟”项目的列表,每个Passo
应该是1,2,3,4和5(按此顺序)。怎么做到这一点?如何在Id
已填充IList<Mock<>>
的情况下获取此Mock<>
列表?也就是配置
Id
负责在我的控制器中创建对象,基本上使用AutoMapper将我的ViewModel对象转换为可以持久化的对象我的存储库中的Model:
passo.SetupGet(x => x.CheckListItens).Returns( ???
问题是我的存储库Fake无法保存对象model = Mapper.Map<TModel>(vm);
,只能保存IEntity
。如何将AutoMapper配置为始终返回Mock<IEntity>
?
答案 0 :(得分:0)
问题1的答案:如果这有帮助,您可以使用闭包来维护正在运行的计数器以用作id。例如:
class MyTestClass
{
int _runningCounter = 0;
public void SomeTest()
{
/* ... some other code including mock creation ...*/
someMock.Setup(m => m.ReturnNewWidgetEntity())
.Returns(() => new WidgetEntity{ Id= ++_runningCounter });
}
}
每次在模拟对象上调用ReturnNewWidgetEntity
时,Id属性将设置为增加的数字。
对问题2的回答:我建议不要对Mapper
类具体依赖,而应将其替换为对IMapperEngine
的注入依赖。 Richard Dingwall在这里解释了这种技术:http://richarddingwall.name/2009/05/07/mocking-out-automapper-with-dependency-injection/
基本上,您在容器中注册Mapper.Engine
作为IMapperEngine
的单例实现,然后在单元测试中对其进行模拟,以便它为您提供所需的Mock<>
类。
我希望这些答案中的一个或两个至少能让您深思熟虑。老实说,跟你的整个例子有点困难!