Dynamics 365 CE/Model-Driven App – Solução Base para Visual Studio (Parte 3/3)


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:

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

2 comentários em “Dynamics 365 CE/Model-Driven App – Solução Base para Visual Studio (Parte 3/3)

  1. 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 😉

    Curtir

Deixe um comentário

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.