Olá pessoal,
Este é o terceiro e último post desta série de como criar uma solução base para projetos D365/Model-Driven App no Visual Studio!
Para ver o que foi discutido anteriormente, veja os links abaixo:
- Dynamics 365 CE/Model-Driven App – Solução Base para Visual Studio (Parte 1/3)
- Dynamics 365 CE/Model-Driven App – Solução Base para Visual Studio (Parte 2/3)
Para baixar a solução de Visual Studio contendo tudo que será discutido a solução completa, vá até o meu GitHub!
Neste post, irei detalhar como gerenciar Web Resources e Testes Unitátios. Além disso, mostrarei como utilizá-los em exemplos práticos.
Vamos iniciar falados de Web Resources!
Client side (Web Resources)
O projeto WebResources é o responsável por armazenar tudo que fazemos no lado client do Dynamics 365, como HTMLs, JScripts, Imagens e etc. Este projeto não precisa de nenhum componente externo, o intuito aqui é de organizar e aproveita o IntelliSense do Visual Studio para acelerar o desenvolvimento.

Criei apenas um arquivo JS para mostrar como podemos organizar nosso client side. No exemplo abaixo, estou deixando o atributo “telephone1” obrigatório se o método preferencial de contato (preferedcontactmethodcode) é igual a “Telefone” (3):
var BaseSolution = window.BaseSolution || {};
(function () {
this.preferredcontactmethodcodeOnChange = function (executionContext) {
var formContext = executionContext.getFormContext();
var preferredcontactmethodcode = formContext.getAttribute("preferredcontactmethodcode");
var telephone1 = formContext.getAttribute("telephone1");
if (telephone1 != null) {
// Set Business Phone optional
telephone1.setRequiredLevel("none");
if (preferredcontactmethodcode != null && preferredcontactmethodcode.getValue() == 3) {
// Set Business Phone required
telephone1.setRequiredLevel("required");
}
}
}
}).call(BaseSolution);
Como disse anteriormente, este projeto tem como finalidade organizar seus web resources e aproveitar as ferramentas do Visual Studio, assim a ideia aqui era fornecer um ponto de partida.
Testes
No primeiro post desta série, eu mencionei que iremos criar nossos Testes Unitários utilizando FakeXrmEasy para facilitar a obtenção do contexto e trabalharmos com os componentes do D365/Model-driven Power App. Pois bem, aqui vamos nós!
Por trata-se de um projeto de testes, precisamos adicionar as dlls dos projetos que desejamos testar (Tests), no nosso caso os projetos de Plugins e Workflows.
Não existirá nenhum problema ao adicionarmos as dlls, porém haverá um problema futuro! O problema no caso está relacionado ao fato de ambos projetos Plugin e Workflow terem uma referência compartilhada (shared) do projeto Base. Assim, o projeto de Testes irá possuir duas referências do mesmo conjunto de classes existentes no projeto Base, inviabilizando a compilação. Porém não se desespere! É simples de resolver este problema!
Para não termos esta dupla referência, devemos criar “apelidos” (alias) para nossas dlls, selecione a dll, clique com o botão direito e depois em Properties. Em seguida altere o valor da propriedade “Aliases” para algo como d365plugins:
Não esqueça de repetir o mesmo procedimento para a outra dll.
Com o problema de dupla referência resolvido, agora podemos criar nossas classes de teste unitário. Mas primeiramente precisamos criar a classe Base do nosso projeto de testes, que possui a mesma ideia das demais classes base que criamos anteriormente, inicializar objeto e deixá-los prontos para a utilização, reduzindo o tempo de criação/desenvolvimento.
Segue o código da classe Base:
using FakeXrmEasy;
namespace D365.BaseSolution.Tests.Common
{
public abstract class BaseClass
{
public XrmFakedContext fakedContext { get; private set; }
protected BaseClass()
{
fakedContext = new XrmFakedContext();
}
}
}
Não vou entrar nos detalhes de como utilizar o FakeXRMEasy framework, mas por ele realmente criar uma cópia falsa (fake) do contexto Dynamics, precisamos um objeto de contexto.
Agora, é o momento de criarmos nossas classes de testes, vou iniciar criando uma para o plugin que criamos anteriormente.
Plugins
extern alias d365plugins;
using System;
using System.Linq;
using Xunit;
using FakeXrmEasy;
using System.Collections.Generic;
using Microsoft.Xrm.Sdk;
using d365plugins.D365.BaseSolution.Plugins;
using d365plugins::D365.BaseSolution.Base.Entities;
namespace D365.BaseSolution.Tests
{
public class TestCreateAccount : Common.BaseClass
{
private Account account;
XrmFakedPluginExecutionContext pluginContext;
private Guid accountId = new Guid("5125f555-8111-ea11-a811-000d3a795fd4");
private void CreateTestContext()
{
account = new Account
{
Id = accountId,
Name = "Account ABC"
};
}
private void CreatePluginContext()
{
ParameterCollection inputParameters = new ParameterCollection();
inputParameters.Add("Target", account);
pluginContext = fakedContext.GetDefaultPluginContext();
pluginContext.MessageName = "Create";
pluginContext.InputParameters = inputParameters;
// Initialize data that are required for the plugin execution, we need to have it into the context in order to create the contact record inside the plugin
fakedContext.Initialize(new List<Entity> { account });
}
[Fact]
private void Success()
{
CreateTestContext();
CreatePluginContext();
fakedContext.ExecutePluginWith<CreateAccount>(pluginContext);
var contact = (from c in fakedContext.CreateQuery<Contact>()
where c.ParentCustomerId != null && c.ParentCustomerId.Id == accountId
select c).FirstOrDefault();
// if contact contains data, it means that plugin has ran successfully
Assert.True(contact != null);
}
//[Fact]
//private void Error()
//{
// CreateTestContext();
// //Break the code to get an error
// account.Id = new Guid("00000000-8111-ea11-a811-000d3a795fd4");
// CreatePluginContext();
// fakedContext.ExecutePluginWith<CreateAccount>(pluginContext);
// var contact = (from c in fakedContext.CreateQuery<Contact>()
// where c.ParentCustomerId != null && c.ParentCustomerId.Id == accountId
// select c).FirstOrDefault();
// // if contact DOESN'T contains data, it means that plugin HASN'T ran successfully, but that's what you are looking for here
// Assert.True(contact != null);
// //Assert.Throws<InvalidPluginExecutionException>(() => fakedContext.ExecutePluginWith<CreateAccount>(pluginContext));
//}
}
}
Analisando o código…
Como disse anteriormente o FakeXrmEasy é um framework que possui diversas funcionalidades que não serão profundamente abordadas neste post, mas acredito que uma ideia de como ele funcionada pode ser entendida ao visualizar o código e meus comentários a seguir.
Já na primeira linha temos que usar o “apelido” (d365plugins) de nossa dll para acessar suas classes, assim começo o código fazendo uma chamada deste alias.
O método CreateTestContext, cria os objetos que serão necessários para o contexto de execução do plugin, no nosso exemplo, apenas precisamos de uma conta (account) já inicializado na memória.
O método CreatePluginContext inicializa os objetos que o plugin necessita para ser executado, pelo plugin que criei ser acionado na criação (create), apenas precisamos criar o target e adicioná-lo na memória.
O teste Success testa um cenário onde o resultado esperado é um sucesso, veja que após “chamar” o plugin faço um request da entidade contatos (contact) para checar se o contato foi criado corretamente pelo plugin.
Já o método Error (no qual deixei comentado de propósito para não gerar erros se você decidir testar meus exemplos), testa um cenário onde algum erro acontece e o plugin não realiza o que estavámos esperando. Neste exemplo, eu troquei o id da conta (account) de próposito para não termos nenhum registro de contato relacionado com a conta pre-adicionada anteriormente.
Debugando seu teste…
Veja que o método Success realizada uma chamada no plugin CreateAccount:
Deste modo, qualquer breakpoint dentro plugin será obedecido, e podemos visualizar o contexto falso que foi criado em memória:
Por fim, podemos checar o resultado do plugin dentro de nosso método de testes:
Workflows
A classe de testes para workflows é muito parecida com a de plugin, porém mais simplificado ainda. Veja o código!
extern alias d365workflows;
using System;
using Xunit;
using FakeXrmEasy;
using System.Collections.Generic;
using Microsoft.Xrm.Sdk;
using d365workflows.D365.BaseSolution.Workflows;
using d365workflows::D365.BaseSolution.Base.Entities;
namespace D365.BaseSolution.Tests.Workflows
{
public class TestSumNumbers : Common.BaseClass
{
private decimal num1;
private decimal num2;
[Fact]
private void Success()
{
num1 = new Decimal(5);
num2 = new Decimal(7);
var inputsParams = new Dictionary<string, object>() {
{ "Number1", num1 },
{ "Number2", num2 }
};
var results = fakedContext.ExecuteCodeActivity<SumNumbers>(inputsParams);
Assert.Equal("12", results["Result"].ToString());
}
//[Fact]
//private void Error()
//{
// num1 = new Decimal(5);
// num2 = new Decimal(7);
// var inputsParams = new Dictionary<string, object>() {
// { "Number1", num1 },
// { "Number2", num2 }
// };
// var results = fakedContext.ExecuteCodeActivity<SumNumbers>(inputsParams);
// Assert.Equal("99", results["Result"].ToString());
//}
}
}
Analisando o código…
Na primeira linha temos que usar o “apelido” (d365workflows) de nossa dll para acessar suas classes, assim começo o código fazendo uma chamada deste alias.
O teste Success testa um cenário onde o resultado esperado é um sucesso, veja que inicializamos os parametros requeridos pelo workflow e os passamos na chamada do método SumNumbers. Após o workflow ser executado, verifique se o resultado retornado é igual ao esperado.
Já o método Error (no qual deixei comentado de propósito para não gerar erros se você decidir testar meus exemplos), testa um cenário onde algum erro acontece e o workflow não realiza o que estavámos esperando. Neste exemplo, estamos fazendo uma soma de 5 + 7, o resultado é 12, porém estou checando se o resultado é 99 só para provocar o erro.
Bom está foi a última etapa de toda esta série de posts que esclareceram em detalhes a solução que criei para servir como a base de seus projetos em D365 CE ou Power Apps Model-driven, o código pode ser acessado no meu GitHub! Espero que o conteúdo lhe ajude e espero em breve aqui!
[]’s,
Tiago




Olá Tiago,
Obrigado por esta série de posts! Ando há procura de uma coisa destas há imenso tempo, tanto como acelerador como para uniformizar o desenvolvimento dentro da organização. Excelente trabalho 😉
CurtirCurtir
Obrigado Bruno, espero ter ajudado!
[]’s,
Tiago
CurtirCurtir