Recently I had to find a way for running a command line process in server. I had to spend fair bit of time googling for various approaches of doing it. Most of them are by using PSExec. However there is another approach of using WMI (Windows Management Instrumentation) . Below is one of the approach , which I found at msdn blog.

Below method can be accessed anywhere by

1
2
ProcessWMI p = new ProcessWMI();
p.ExecuteRemoteProcessWMI(remoteMachine, sBatFile, timeout);

The solution has multiple parts as follows

  1. Connect to remote machine using remote machine Name, user name and password
  2. Start the remote process. Win32 process and pass the command to be run
  3. Find if the remote process is running and if it does, start an event monitor to wait for it to exit
  4. Once the process exits, retrieve its exit code

Code below is taken from above MSDN link ( Just to make sure it is available for me even if original MSDN link is unavailable in future.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
public class ProcessWMI
{
    public uint ProcessId;
    public int ExitCode;
    public bool EventArrived;
    public ManualResetEvent mre = new ManualResetEvent(false);
    public void ProcessStoptEventArrived(object sender, EventArrivedEventArgs e)
    {
        if ((uint)e.NewEvent.Properties["ProcessId"].Value == ProcessId)
        {
            Console.WriteLine("Process: {0}, Stopped with Code: {1}", (int)(uint)e.NewEvent.Properties["ProcessId"].Value, (int)(uint)e.NewEvent.Properties["ExitStatus"].Value);
            ExitCode = (int)(uint)e.NewEvent.Properties["ExitStatus"].Value;
            EventArrived = true;
            mre.Set();
        }
    }
    public ProcessWMI()
    {
        this.ProcessId = 0;
        ExitCode = -1;
        EventArrived = false;
    }
    public void ExecuteRemoteProcessWMI(string remoteComputerName, string arguments, int WaitTimePerCommand)
    {
        string strUserName = string.Empty;
        try
        {
            ConnectionOptions connOptions = new ConnectionOptions();
            //Note: This will connect  using below credentials. If not provided, it will be based on logged in user
             connOptions.Username = ConfigurationManager.AppSettings["RemoteMachineLogonUser"];
             connOptions.Password = ConfigurationManager.AppSettings["RemoteMachineUserPassword"];

            connOptions.Impersonation = ImpersonationLevel.Impersonate;
            connOptions.EnablePrivileges = true;
            ManagementScope manScope = new ManagementScope(String.Format(@"\\{0}\ROOT\CIMV2", remoteComputerName), connOptions);

            try
            {
                manScope.Connect();
            }
            catch (Exception e)
            {
                throw new Exception("Management Connect to remote machine " + remoteComputerName + " as user " + strUserName + " failed with the following error " + e.Message);
            }
            ObjectGetOptions objectGetOptions = new ObjectGetOptions();
            ManagementPath managementPath = new ManagementPath("Win32_Process");
            using (ManagementClass processClass = new ManagementClass(manScope, managementPath, objectGetOptions))
            {
                using (ManagementBaseObject inParams = processClass.GetMethodParameters("Create"))
                {
                    inParams["CommandLine"] = arguments;
                    using (ManagementBaseObject outParams = processClass.InvokeMethod("Create", inParams, null))
                    {

                        if ((uint)outParams["returnValue"] != 0)
                        {
                            throw new Exception("Error while starting process " + arguments + " creation returned an exit code of " + outParams["returnValue"] + ". It was launched as " + strUserName + " on " + remoteComputerName);
                        }
                        this.ProcessId = (uint)outParams["processId"];
                    }
                }
            }

            SelectQuery CheckProcess = new SelectQuery("Select * from Win32_Process Where ProcessId = " + ProcessId);
            using (ManagementObjectSearcher ProcessSearcher = new ManagementObjectSearcher(manScope, CheckProcess))
            {
                using (ManagementObjectCollection MoC = ProcessSearcher.Get())
                {
                    if (MoC.Count == 0)
                    {
                        throw new Exception("ERROR AS WARNING: Process " + arguments + " terminated before it could be tracked on " + remoteComputerName);
                    }
                }
            }

            WqlEventQuery q = new WqlEventQuery("Win32_ProcessStopTrace");
            using (ManagementEventWatcher w = new ManagementEventWatcher(manScope, q))
            {
                w.EventArrived += new EventArrivedEventHandler(this.ProcessStoptEventArrived);
                w.Start();
                if (!mre.WaitOne(WaitTimePerCommand,false))
                {
                    w.Stop();
                    this.EventArrived = false;
                }
                else
                    w.Stop();
            }
            if (!this.EventArrived)
            {
                SelectQuery sq = new SelectQuery("Select * from Win32_Process Where ProcessId = " + ProcessId);
                using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(manScope, sq))
                {
                    foreach (ManagementObject queryObj in searcher.Get())
                    {
                        queryObj.InvokeMethod("Terminate", null);
                        queryObj.Dispose();
                        throw new Exception("Process " + arguments + " timed out and was killed on " + remoteComputerName);
                    }
                }
            }
            else
            {
                if (this.ExitCode != 0)
                    throw new Exception("Process " + arguments + "exited with exit code " + this.ExitCode + " on " + remoteComputerName + " run as " + strUserName);
                else
                    Console.WriteLine("process exited with Exit code 0");
            }

        }
        catch (Exception e)
        {
            throw new Exception(string.Format("Execute process failed Machinename {0}, ProcessName {1}, RunAs {2}, Error is {3}, Stack trace {4}", remoteComputerName, arguments, strUserName, e.Message, e.StackTrace), e);
        }
    }
}

Code snippet for running power shell on a remote machine. Loosely based on blog post here and here

Code below is based on the sample code given in above two links

Add reference to System.Management.Automation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using System.Management.Automation;
using System.Management.Automation.Runspaces;


  internal void runPowershellRemotely(string location, string scriptToBeRun)
        {
            string userName = ConfigurationManager.AppSettings["RemoteMachineLogonUser"];
            string password = ConfigurationManager.AppSettings["RemoteMachineUserPassword"];
           var securestring = new SecureString();
            foreach (Char c in password){
                securestring.AppendChar(c);
            }

            PSCredential creds = new PSCredential(userName, securestring);
            // Remove logging if not needed
            log.Info(String.Format("\tPOWERSHEL : Running Powershell {0} at location {1}", scriptToBeRun, location));
            WSManConnectionInfo connectionInfo = new WSManConnectionInfo();

           connectionInfo.ComputerName = ConfigurationManager.AppSettings["RemoteMachine"];
            connectionInfo.Credential = creds;
            Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo);
            runspace.Open();
            using (PowerShell ps = PowerShell.Create())
            {
                ps.Runspace = runspace;
                ps.AddScript(@"cd "+ location);
                ps.AddScript(scriptToBeRun);
                try
                {
                    var results = ps.Invoke();
                    log.Info("\tPOWERSHEL : Results from Powershell Script is ---------------------------");
                    foreach(var x in results)
                    {
                        log.Info(x.ToString());
                    }
                    log.Info("\tPOWERSHEL : End of results--------------------------------- ---------------------------");
                }
                catch (Exception e)
                {
                    log.Error("\tPOWERSHEL : Exception from running Powershell Script is" + e.ToString());
                }

            }
            runspace.Close();
        }

Below is code snippet for working with windows service. It helps to find status of service, start , stop and restart as required.

We need to pass in details of windows services name ( as shown i services.msc ) and machine name(should be in same network).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System.ServiceProcess;


          internal string FindStatus(string service, string server)
        {
            var myService = new ServiceController(service, server);
            log.Info(String.Format("\tStatus of {0} service in {1} is {2}", service, server, myService.Status.ToString()));
            return myService.Status.ToString();
        }

        internal string StopService(string service, string server)
        {
            var myService = new ServiceController(service, server);
            if (myService.Status == ServiceControllerStatus.Running)
            {
                myService.Stop();
                myService.WaitForStatus(ServiceControllerStatus.Stopped);
                log.Info(String.Format("\t{0} service Stopped in {1}. Current Status is {2}", service, server, myService.Status.ToString()));
            }
            return myService.Status.ToString();
        }

        internal string StartService(string service, string server)
        {
            var myService = new ServiceController(service, server);
            if (myService.Status == ServiceControllerStatus.Stopped)
            {
                myService.Start();
                myService.WaitForStatus(ServiceControllerStatus.Running);
                log.Info(String.Format("\t{0} service Started in {1}. Current Status is {2}", service, server, myService.Status.ToString()));
            }
            return myService.Status.ToString();
        }

Code snippet for comparing two xml files without using xsd for validating their structure is same ( nodes and arguments should be same. Values of each node/argument can be different).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
internal void VerifyMessageHaveSimilarStructureOfTemplate(string inputXml, string templateXml)
        {
            var docA = new XmlDocument();
            var docB = new XmlDocument();

            docA.LoadXml(inputXml);
            docB.LoadXml(templateXml);

            var isDifferent = DoTheyHaveDiferentStructure(docA.ChildNodes, docB.ChildNodes);
            log.Info("Result of Checking for difference of Input xml with template is : " + isDifferent.ToString());


        }


  private bool DoTheyHaveDiferentStructure(XmlNodeList xmlNodeListA, XmlNodeList xmlNodeListB)
        {
            if (xmlNodeListA.Count != xmlNodeListB.Count) return true;

            for (var i = 0; i < xmlNodeListA.Count; i++)
            {
                var nodeA = xmlNodeListA[i];
                var nodeB = xmlNodeListB[i];

                if (nodeA.Attributes == null)
                {
                    if (nodeB.Attributes != null)
                        return true;
                    else
                        continue;
                }

                if (nodeA.Attributes.Count != nodeB.Attributes.Count || nodeA.Name != nodeB.Name) return true;
                List<string> AttributeNameA = new List<string>();
                List<string> AttributeNameB = new List<string>();
                for (var j = 0; j < nodeA.Attributes.Count; j++)
                {
                    AttributeNameA.Add(nodeA.Attributes[j].Name);
                    AttributeNameB.Add(nodeB.Attributes[j].Name);
                    // -- If attribute position should be same, then include below as well
                    //var attrA = nodeA.Attributes[j];
                    //var attrB = nodeB.Attributes[j];

                    //if (attrA.Name != attrB.Name) return true;
                }
                AttributeNameA.Sort();
                AttributeNameB.Sort();
                if(! AttributeNameA.SequenceEqual(AttributeNameB)) return true;

                if (nodeA.HasChildNodes && nodeB.HasChildNodes)
                {
                    return HaveDiferentStructure(nodeA.ChildNodes, nodeB.ChildNodes);
                }
                else
                {
                    return true;
                }
            }
            return false;
        }

There are many cases where we will have to convert Dataset into list of objects. Below is a generic method using reflection to achieve that.

Below will work only if datatable column name and class property name are same and they match exactly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System.Reflection
internal static List<T> ConvertDataTableToList<T>(DataTable dt)
{
    List<T> data = new List<T>();
    foreach (DataRow row in dt.Rows)
    {
        T item = GetItem<T>(row);
        data.Add(item);
    }
    return data;
}
internal static T GetItem<T>(DataRow dr)
{
    Type temp = typeof(T);
    T obj = Activator.CreateInstance<T>();

    foreach (DataColumn column in dr.Table.Columns)
    {
        foreach (PropertyInfo pro in temp.GetProperties())
        {
            if (pro.Name == column.ColumnName)
                pro.SetValue(obj, dr[column.ColumnName], null);
            else
                continue;
        }
    }
    return obj;
}

Usage of this will be like below

1
2
3
orderDetailsList = ConvertDataTable< OrderDetails >(orderdetailDatatable);

// orderdetailDataset.Table[0] can be used

Cypress is not just UI automation tool . It can be used for testing APIs as well . Even though we have other tools like Postman, Newman, Rest Assured, SOAP UI etc for testing APIs, I believe cypress is a good alternative for testing API. It will help to use same tool for both UI and API test automation.

Demo

Let us look at a sample API test case. In below example, we trigger a API call to http://services.groupkt.com/country/get/iso2code/AU and validate below in the response.

  • Status code of response is 200.
  • Header include ‘application/json’.
  • Body contain “Country found matching code [AU].”

We can then extend this to do any further checks if needed.

Create a new file inside Integration folder of cypress and copy below code into that.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
describe('API Testing with Cypress', () => {
    var result
    
    it('Validate the header', () => {
       result = cy.request('http://services.groupkt.com/country/get/iso2code/AU')
      
       result.its('headers')
             .its('content-type')
             .should('include', 'application/json')
        
    })

    it('Validate the status', () => {
        result = cy.request('http://services.groupkt.com/country/get/iso2code/AU')
       
        result.its('status')
              .should('equal',200);
     })

     it('Validate the body ', () => {
        result = cy.request('http://services.groupkt.com/country/get/iso2code/AU')
       
        result.its('body')
              .its('RestResponse.messages')
              .should('include', 'Country found matching code [AU].');
      
     })
}) 

Open Cypress by running node_modules/.bin/cypress open inside cypress root folder. This will open up Cypress.

Run newly created test.

APITestingWithCypress

Results of test execution will look like below.

[APITestingWithCypress]

Expand each of them and right click on the asserts and inspect the element. This will open up chrome developer tool. Select the console tab , which will list down details of calls made, request received and assertions performed. It will help to write additional assertions, investigate any failure etc.

[APITestingWithCypress]

When we talk about UI automation for browsers, the default tool which comes to mind is Selenium. There are different wrappers around selenium like protractor, Nightwatch , selenium webdriver etc. All of them are build on top of selenium and have all advantages /disadvantages of selenium. All of the control browser by executing remote commands through Network. We will most probably need additional libraries, framework etc to make full use of selenium.

Cypress.io is an open source UI automation tool which can be used for UI testing . Unlike others, this is not build on top of selenium . Instead is a complete new architecture and run in same run loop as browser. So it is running inside browser and have access to almost everything happening inside and outside browser. It is a complete set of tools that you will require to create and run E2E UI automation test cases. Team who developed cypress has made few design trade off which causes some disadvantages to cypress. There is no right tool for automation . It will depend on multiple factors.

Installing Cypress

We can install cypress using npm. Run below command inside project folder to install cypress and all dependencies.

1
npm install cypress --save-dev

Another way of using cypress is to download zip file from here . Just extract the file and start using it.

Opening Cypress

Cypress can be opened by running node_modules/.bin/cypress open command in terminal under /cypress.

If you have downloaded the zip file, you can open cypress by double clicking on the cypress executable.

Write your first test

Cypress already come with predefined example of KitchenSink application which will help you to identify various commands which can be used. It can be found under cypress\integration\example_spec.js.

Let us look at how to write a new test .

Create a new test script file called demotest.js under {project_location}\cypress\integration. Open up the file and write below code into it.

This code will open browser, load google and search for cypress.io and open up the first link.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
describe('My first test for cypress', function() {
    it('Visits google home page ', function() {
      cy.visit('https://google.com');
    })
    it('should load the Google Homepage', () => {
        cy.title().should('eql', 'Google');
    })
    it('should search and open cypress home page', () => {
        cy.get('#lst-ib').type('cypress.io');
        cy.get('[value="I\'m Feeling Lucky"]').focus().click();
    })
   
   
  })

Note: If cross origin policy error is shown, flow the workarounds mentioned.

How does cypress.io compare with Selenium

As mentioned earlier, there is no right or wrong tool for automation. It all depends on suitability for the task on hand. Let us compare few features where cypress.io and selenium have differences.

  • Cross browser support - At this point selenium have more cross browser support that cypress. Cypress supports only chrome variants. You can read about them here
  • Debugging capability - This is high in cypress. I found that error message are more details and infact provide some more details about how to fix it. Also you have full access to chrome dev tools.
  • Keypress - As of now , cypress doesnt support pressing Tab key . You can read about it here.
  • Since cypress is build on node.js, we can chain commands together
  • Cypress.io have built in support for test framework and assertion libraries like mocha, chai etc
  • Cypress.io test cases can be written in Javascript
  • Cypress.io handles wait times better than selenium
  • Cypress.io have hot reloading of test cases. When we make changes to test cases and save it , the test rerun by itself. This is very effective to reduce time spend on building and rerunning selenium based test cases.
  • Cypress.io have built in time travel and screenshots which will help us to go back to failure points and debug. It also capture before and after state for all actions

I will keep adding to this when I play more with cypress.

In previous blog post, we saw how to use BDD format for writing test cases in postman. Most important part of writing tests in postman is understanding various features available. Let us explore various options available . The examples specified in postman documentation, have lot of information about how to setup postman bdd, use chai http assertions, create custom assertions and use before and after hooks. Please import them into postman and try that by yourself to familiarise with postman BDD. Below is only few examples from them.

Postman BDD makes use of Chai Assertion Library and Chai-Http. We have access to both libray and postman scripting environment for writing test cases. Chai has two types of assertion styles.

  • Expect/should for BDD
  • Assert for TDD

Both styles support chainable language to construct assertions. We can use both of them to write postman test assertions. If you need details of all chainable constructs, please refer to their documentation. Major ones which we may use in postman tests are

  • Chains

      to
      be
      been
      is
      that
      which
      and
      has
      have
      with
      at
      of
      same
      but
      does
    
  • Not - Negates all conditions

  • any -
  • all -
  • inlcude
  • OK
  • true
  • false
  • null
  • undefined
  • exist
  • empty
  • match(re[, msg])

Chai-Http module provide various assertions. Read through their documentation here to know details. Below are main commands at our disposal for validation .

  • .status(code)
  • .header (key[, value])
  • .headers
  • .ip
  • .json / .text / .html
  • .redirect
  • .param
  • .cookie

Postman bdd provide response object on which we do most of assertions. It will have all information like response.text, response.body, response.status, response.ok , response.error. Postman BDD will automatically parse JSON and XML responses and hence there is no need to call JSON.parse() or xml2json(). response.text will have unparsed content. It also have automatic error handling , which will allow to continue with other test even if something fails.

Examples for various assertions done on response object are below

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
\\Verifying Header information
expect(response).to.have.status(500);
expect(response).to.have.header('x-api-key');
expect(response).to.have.header('content-type', 'text/plain');
expect(request).to.have.header('content-type', /^text/);
expect(response).to.have.headers;
expect('127.0.0.1').to.be.an.ip;

\\Verifying Response body
expect(response).to.be.json;
expect(response).to.be.html;
expect(response).to.be.text;

response.should.have.status(200); 
response.body.should.not.be.empty;
response.ok.should.be.true;            // sucess with code 2XX
response.error.should.be.true; //failures

\\Verifying request
expect(req).to.have.param('orderby', 'date');
expect(req).to.not.have.param('orderby');
expect(req).to.have.cookie('session_id', '1234');
expect(req).to.not.have.cookie('PHPSESSID');


If we use above assertions in proper BDD format, it will look like below

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
eval(globals.postmanBDD);
describe('Example for Blog using SHOULD', function(){
   it("Tests using SHOULD", function() {
      response.should.have.status(200); 
      response.should.not.be.empty;
      response.should.have.header('content-type', 'application/json; charset=utf-8');
      response.type.should.equal('application/json');
      
      var user = response.body.results[0];
      user.name.should.be.an('object');
      user.name.should.have.property('first').and.not.empty;
      //user.name.should.have.property('first','david');
      user.should.have.property('gender','male');
   
   
   }) 
})
describe('Example for Blog using Expect', function(){
   it("Tests using EXPECT", function() {
      expect(response).to.have.status(200);
      expect(response).not.empty;
      expect(response).to.be.json;
      expect(response).to.have.header('content-type', 'application/json; charset=utf-8');
   }) 
})

it('should contain the un-parsed JSON text', () => {
    response.text.should.be.a('string').with.length.above(50);
    response.text.should.contain('"results":[');
});

Postman

In Previous blog post,we discussed about how to use postman and how to use collections using newman and data file. If you haven’t read that , please have a read through first .

In previous examples, we discussed about writing tests/assertions in postman. We followed normal Javascript syntax for writing test cases including asserting various factors of response ( like content , status code etc). Eventhough this is a straightforward way of writing, many people would like to use existing javascript test library like Mocha. They can use postman - bdd libraries.

Let us take a deep dive into how to use setup postman bdd.

Note: It is assumed that user already have postman and newman installed on their machine along with their dependencies.

Installing Postman BDD

Installation is done triggering a Get request and setting the response as Global environment variable.

  • Create a GET request to http://bigstickcarpet.com/postman-bdd/dist/postman-bdd.js
  • Set Global environment variable by using below command in test tab. postman.setGlobalVariable('postmanBDD', responseBody);

PostManRequest

Once we trigger above get request, postman bdd will be available for use. We can make use of postman BDD features by below command eval(globals.postmanBDD);

Writing Tests

Postman bdd library provide us with flexibility to write tests and assertions using fluent asserts and have best features of Chai and Mocha. Inorder to demonstrate this, I am using sample Tutorial given with postman client.

Open up the sample Request in Postman Tutorial folder under collections. It will already have some test predefined in Test tab. Remove them and add below test to it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
eval(globals.postmanBDD)
//eval(postman.getGlobalVariable('postmanBDD'));
var jsonData = JSON.parse(responseBody);
describe('Testing Sample Request in Postman Tutorial', function () {
  it('CASE 1: Should respond with statusCode = 200', function () {
      response.should.have.status(200);
  });
  it('CASE 2: Should response time less than 500 ms', function () {
      pm.response.responseTime.should.be.below(500);
  });
  it('CASE 3: User ID should be 1', function () {
      jsonData.userId === 1;
  });

});

Note: You can find more details of various type of asserts in http://www.chaijs.com/api/bdd/

Once it is done, trigger the request

PostManRequest.

Gulp is a toolkit for automating painful or time-consuming task in your development workflow, so you can stop messing around and build something. Gulp can be used for creating a simple task to run automated test cases.

Firstly, we will create package.json file for this project. This can be done by below command from project folder. It will prompt you to enter a list of information required for creating package.json file

npm init

Once this is done, install gulp. It can be done by below command. This will add gulp as a dev dependency.

npm install --save-dev gulp-install

In order to run acceptance test cases, we will need to install nunit/xunit test runners. It can be done by below command from the root folder.

npm install --save-dev gulp-nunit-runner
        OR
npm install --save-dev gulp-xunit-runner

Detailed usage of above test runners are available here.

Once above are installed, we need to create gulpfile.js inside root folder. This file will have details of various gulp tasks

Sample Usage of test runner is below. Insert this code into gulpfile.js

1
2
3
4
5
6
7
8
9
10
11
12
13

var gulp = require('gulp'),
    nunit = require('gulp-nunit-runner');
 
gulp.task('unit-test', function () {
    return gulp.src(['**/*.Test.dll'], {read: false})
        .pipe(nunit({
            executable: 'C:/nunit/bin/nunit-console.exe',
            options : {
              where : 'cat == test'
            }
        }));
});
  • {read: false} means, it will read only file names and not the entire file.
  • Executable is the path to nunit console runner, which should be available.
  • gulp.src is that path to acceptance test solution dll. Since we use wild character, we may have to modify this path to reflect the exact path of dll.( something like ./**/Debug/Project.acceptancetest.dll)

Once we have above in gulpfile.js, it can be run by below command

gulp unit-test

Out of above command will be something like

C:/nunit/bin/nunit-console.exe "C:\full\path\to\Database.Test.dll" "C:\full\path\to\Services.Test.dll"

Note: If it complains about assembly missing, it means path to acceptance test solution is incorrect . Retry after fixing the path.

Gulp Nunit runner provide lot options to configure test run, like selecting test cases based on category, creating output files etc. Detailed options can be found here.

Below is an example with few options

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

var gulp = require('gulp'),
    nunit = require('gulp-nunit-runner');
 
gulp.task('unit-test', function () {
    return gulp.src(['**/*.Test.dll'], {read: false})
        .pipe(nunit({
            executable: 'C:/nunit/bin/nunit-console.exe',
            options : {
              where : 'cat == test',
              work : 'TestResultsFolder',
              result : 'TestResults.xml',
              config : 'Debug'
            }
        }));
});
  • Where - Selects the category which needs to be run
  • Work - Create a folder with specified path/name for output files
  • result - create test results in xml
  • config - select the config which needs to be run

If we run gulp unit-test now, it will execute only the test cases having category test. It will create a folder named TestResultsFolder and will have an xml report of the test run inside it . The folder will be created in root where we have gulpfile.js.