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