using System;
using Moq;
namespace MoqSample
{
class Program
{
static void Main(string[] args)
{
// ARRANGE
decimal amount = 100;
// that's why you will love unit testing, mishaps could be avoided :-)
string errorMessage = "Could be an anomaly, passed amount is different from amount to transfer";
var mockedSource = new Mock<IAccount>();
mockedSource.Setup(x => x.Withdraw(It.Is<decimal>(a => a != amount))).Throws(new Exception(errorMessage));
mockedSource.Setup(x => x.Deposit(It.Is<decimal>(a => a != amount))).Throws(new Exception(errorMessage));
var mockedDest = new Mock<IAccount>();
mockedDest.Setup(x => x.Withdraw(It.Is<decimal>(a => a != amount))).Throws(new Exception(errorMessage));
mockedDest.Setup(x => x.Deposit(It.Is<decimal>(a => a != amount))).Throws(new Exception(errorMessage));
// ACT
int test = 0;
/*
* 0's error: NONE
*
* 1's error:
* Expected invocation on the mock should never have been performed, but was 1 times: source => source.Deposit(.amount)
*
* 2's error:
* Expected invocation on the mock once, but was 2 times: dest => dest.Deposit(.amount)
*
* 3's error:
* Expected invocation on the mock once, but was 0 times: dest => dest.Deposit(.amount)
*
* 4's error:
* Expected invocation on the mock once, but was 0 times: source => source.Withdraw(.amount)
*
* 5's error:
* Could be an anomaly, passed amount is different from transfer amount
*
* */
if (test == 0)
TransactionMaker.TransferFund(mockedSource.Object, mockedDest.Object, amount);
else if (test == 1)
TransactionMaker.TransferFundBuggy1(mockedSource.Object, mockedDest.Object, amount);
else if (test == 2)
TransactionMaker.TransferFundBuggy2(mockedSource.Object, mockedDest.Object, amount);
else if (test == 3)
TransactionMaker.TransferFundBuggy3(mockedSource.Object, mockedDest.Object, amount);
else if (test == 4)
TransactionMaker.TransferFundBuggy4(mockedSource.Object, mockedDest.Object, amount);
else if (test == 5)
TransactionMaker.TransferFundAnomalous(mockedSource.Object, mockedDest.Object, amount);
else
throw new Exception("Select test case from 0 to 5");
// ASSERT
mockedSource.Verify(source => source.Withdraw(amount), Times.Once());
mockedSource.Verify(source => source.Deposit(amount), Times.Never());
mockedDest.Verify(dest => dest.Deposit(amount), Times.Once());
mockedDest.Verify(dest => dest.Withdraw(amount), Times.Never());
}
}
public static class TransactionMaker
{
public static void TransferFund(IAccount sourceAccount, IAccount destAccount, decimal amountToTransfer)
{
sourceAccount.Withdraw(amountToTransfer);
destAccount.Deposit(amountToTransfer);
}
public static void TransferFundBuggy1(IAccount sourceAccount, IAccount destAccount, decimal amountToTransfer)
{
sourceAccount.Withdraw(amountToTransfer);
sourceAccount.Deposit(amountToTransfer);
}
public static void TransferFundBuggy2(IAccount sourceAccount, IAccount destAccount, decimal amountToTransfer)
{
sourceAccount.Withdraw(amountToTransfer);
destAccount.Deposit(amountToTransfer);
destAccount.Deposit(amountToTransfer);
}
public static void TransferFundBuggy3(IAccount sourceAccount, IAccount destAccount, decimal amountToTransfer)
{
sourceAccount.Withdraw(amountToTransfer);
}
public static void TransferFundBuggy4(IAccount sourceAccount, IAccount destAccount, decimal amountToTransfer)
{
sourceAccount.Deposit(amountToTransfer);
}
public static void TransferFundAnomalous(IAccount sourceAccount, IAccount destAccount, decimal amountToTransfer)
{
sourceAccount.Withdraw(amountToTransfer);
destAccount.Deposit(amountToTransfer + 0.5M);
}
}
public interface IAccount
{
void Withdraw(decimal v);
void Deposit(decimal v);
}
public class Account : IAccount
{
public void Withdraw(decimal amount)
{
}
public void Deposit(decimal amount)
{
}
}
}
"Simplicity can't be bought later, it must be earned from the start" -- DB
Monday, February 27, 2012
A primer on unit testing with Moq
The following code is a primer on unit testing, just get Moq from Nuget. Note: Actual unit testing should be done on a separate project, not on Main method :-) This code just demonstrates how elegant Moq is for unit testing needs.
Monday, January 23, 2012
Liskov Substitution Principle, a simple example
The Liskov Substitution Principle of object oriented design states:
The code above violates Liskov Substitution Principle, there should be no side effects on results even I use Square object for Rectangle purposes. Don't override properties just because it look smart to do so.
To set the Square's sides properly, set the two sides via one property only:
In class hierarchies, it should be possible to treat a specialized object as if it were a base class object.
using System;
namespace Liskov
{
class Rectangle
{
int _height;
public virtual int Height
{
get{
return _height;
}
set {
_height = value;
}
}
int _width;
public virtual int Width
{
get {
return _width;
}
set {
_width = value;
}
}
public int Area
{
get {
return _width * _height;
}
}
public Rectangle (int width, int height)
{
Height = height;
Width = width;
}
}
class Square : Rectangle
{
public override int Height {
set {
base.Height = value;
base.Width = value;
}
}
public override int Width {
set {
base.Width = value;
base.Height = value;
}
}
public Square (int side) : base(width: side, height: side)
{
}
}
class MainClass
{
public static void Main (string[] args)
{
Rectangle s = new Square(side: 10);
// 100, correct
Console.WriteLine ("Area: {0}", s.Area);
// reusing the object r as rectangle object
s.Height = 4;
s.Width = 3;
// expecting 12, actual output is 9. incorrect. Liskov Substitution Principle is violated
Console.WriteLine ("Area: {0}", s.Area);
}
}
}
The code above violates Liskov Substitution Principle, there should be no side effects on results even I use Square object for Rectangle purposes. Don't override properties just because it look smart to do so.
To set the Square's sides properly, set the two sides via one property only:
public int Side {
get {
return Height;
}
set {
Height = Width = value;
}
}
Web Sharing not working with mod_mono
If Web Sharing is not working after you upgraded to Lion, and you happen to configured ASP.NET Mono on your Snow Leopard before. Just do these 5 steps:
1. download the latest mod_mono Apache module
2. extract the content of the archive
3. do these on Terminal:
4. Click Web Sharing
5. No step five. Voila! Happy .NET computing on Mac and Mono again
No tedious steps taken, if the above steps doesn't work for you. Try this:
http://stackoverflow.com/questions/8815403/trouble-installing-mod-mono-on-mac-osx-lion
How to rule out that it is not a problem on Apache per se (most articles explain how to fix Apache itself) ? Use sudo apachectl configtest. Using that, I saw error message on httpd.conf regarding Mono, that makes googling easier for me, I narrowed down my search, and arrived on stackoverflow's solution.
P.S. I forgot to copy the exact mod_mono error on httpd.conf, it's something like: syntax error on httpd.conf dlopen mod_mono.so
1. download the latest mod_mono Apache module
2. extract the content of the archive
3. do these on Terminal:
./configure make sudo make install
4. Click Web Sharing
5. No step five. Voila! Happy .NET computing on Mac and Mono again
No tedious steps taken, if the above steps doesn't work for you. Try this:
http://stackoverflow.com/questions/8815403/trouble-installing-mod-mono-on-mac-osx-lion
How to rule out that it is not a problem on Apache per se (most articles explain how to fix Apache itself) ? Use sudo apachectl configtest. Using that, I saw error message on httpd.conf regarding Mono, that makes googling easier for me, I narrowed down my search, and arrived on stackoverflow's solution.
P.S. I forgot to copy the exact mod_mono error on httpd.conf, it's something like: syntax error on httpd.conf dlopen mod_mono.so
Sunday, November 27, 2011
Flexigrid CRUD(inline form) with ASP.NET MVC
In this post, I'll show you the supporting code for placing a form within flexigrid.
First, declare a variable for the html string of your form:
We use canRender so we can defer the execution of javascript of the form's html string
Here's the flexigrid setup and getting the form's html string:
Lastly we put this at the end of the html:
Here's the Flexigrid form-inliner helper functions:
Flexigrid CRUD(inline form) example with ASP.NET MVC, complete with model validation:
http://code.google.com/p/flexigrid-crud-example/downloads/list
Known issues:
1. Not with flexigrid per se; but when inside of flexigrid, the jQuery Ajax ComboBox's result area is far from its textbox when using Firefox or IE, have made a work-around in Chrome though. If you could lend a help in correcting that problem of jQuery Ajax ComboBox, I'll be more than glad to accept a patch.
2. If the flexigrid has no rows, the inline form is not in full width. Accepting patch for flexigrid code or helper code.
First, declare a variable for the html string of your form:
<script type="text/javascript">
var formHtml = "";
function canRender() {
return _canRender;
}
$(function () {
_canRender = false;
});
</script>
We use canRender so we can defer the execution of javascript of the form's html string
<div id="editor" style="visibility: hidden">
@using (Html.BeginForm(null, null, FormMethod.Post, new { id = "theForm" }))
{
<fieldset>
<legend>Person</legend>
@Html.HiddenFor(x => x.PersonId)
@Html.HiddenFor(x => x.RowVersion)
<div class="editor-label">
@Html.LabelFor(x => x.Username)
</div>
<div class="editor-field">
@Html.EditorFor(x => x.Username)
@Html.ValidationMessageFor(x => x.Username)
</div>
<div class="editor-label">
@Html.LabelFor(x => x.Firstname)
</div>
<div class="editor-field">
@Html.EditorFor(x => x.Firstname)
@Html.ValidationMessageFor(x => x.Firstname)
</div>
<div class="editor-label">
@Html.LabelFor(x => x.Lastname)
</div>
<div class="editor-field">
@Html.EditorFor(x => x.Lastname)
@Html.ValidationMessageFor(x => x.Lastname)
</div>
<div class="editor-label">
@Html.LabelFor(x => x.Country.CountryId)
</div>
<div class="editor-field">
<table style="border-style: none">
<tr>
<td style="border-style: none">
@Html.AjaxComboBoxFor(x => x.Country.CountryId, "/Countries/Management/Lookup/", "/Countries/Management/Caption/",
new { }, new { sub_info = true, can_render_callback = "canRender", textbox_width = 200 })
</td>
<td style="border-style: none">
@Html.ValidationMessageFor(x => x.Country.CountryId)
</td>
</tr>
</table>
</div>
<div class="editor-label">
@Html.LabelFor(x => x.FavoriteNumber)
</div>
<div class="editor-field">
@Html.EditorFor(x => x.FavoriteNumber)
@Html.ValidationMessageFor(x => x.FavoriteNumber)
</div>
<p>
<input type="submit" value="Save" />
<input type="button" id="Closer" value="Close" />
</p>
</fieldset>
<div style="max-width: 500px; width: 500px;">
@Html.JsAccessibleValidationSummary(excludePropertyErrors: true)
</div>
}
<script type="text/javascript">
$(function () {
if (!canRender()) return;
var scope = $('#theForm');
parseDynamicContent(scope);
$('#Closer', scope).click(function (e) {
closeForm($(scope));
});
$('input[type=submit]', scope).click(function (e) {
try {
e.preventDefault();
if (!scope.valid()) {
// alert('has invalid');
return;
}
save(scope);
// closeForm(scope);
} catch (e) {
alert("Error " + e);
}
});
$(scope).attr('id', guid());
});
function save(scope) {
$.ajax({
url: '/People/Management/SaveViaAjax',
type: 'POST',
data: $(scope).serialize(),
success: function (result) {
var isOk = $(scope).modelValidation(result);
if (isOk) {
var isNew = $('#PersonId', scope).val() == '';
if (isNew) {
$('#PersonId', scope).val(result.PersonId);
}
$('#RowVersion', scope).val(result.RowVersion);
if (isNew) {
$(scope).closest('table').flexReload();
}
else {
setFgEditText(scope, 'Username', $('#Username', scope).val());
setFgEditText(scope, 'Firstname', $('#Firstname', scope).val());
setFgEditText(scope, 'Lastname', $('#Lastname', scope).val());
setFgEditText(scope, 'FavoriteNumber', $('#FavoriteNumber', scope).val());
setFgEditText(scope, 'Country', $('#Country_CountryId', scope).ajc().getText());
closeForm(scope);
}
}
// $('#firstTable').flexReload();
},
error: function (a, b, c) {
alert(a.statusText);
alert(b);
alert(c);
}
}); //ajax
}//save
</script>
</div>
Here's the flexigrid setup and getting the form's html string:
<script type="text/javascript">
$(function () {
// main..
setupForm();
setupFirstTable();
// ..main
});
function setupForm() {
formHtml = $('#editor').html();
$('#editor').remove();
}
function setupFirstTable() {
$('#firstTable').flexigrid({
url: '/People/Management/List',
dataType: 'json',
colModel: [
{ display: 'User', name: 'Username', width: 150, sortable: true, align: 'left' },
{ display: 'Firstname', name: 'Firstname', width: 150, sortable: true, align: 'left' },
{ display: 'Lastname', name: 'Lastname', width: 150, sortable: true, align: 'left' },
{ display: 'Favorite#', name: 'FavoriteNumber', width: 150, sortable: true, align: 'left' },
{ display: 'Country', name: 'Country', width: 150, sortable: true, align: 'left' },
{ display: 'RowVersion', name: 'RowVersion', width: 150, sortable: true, align: 'left', hide: true }
],
buttons: [
{ name: 'Add', bclass: 'add', onpress: add },
{ separator: true },
{ name: 'Edit', bclass: 'edit', onpress: edit },
{ separator: true },
{ name: 'Delete', bclass: 'delete', onpress: del }
],
singleSelect: true,
sortname: 'Lastname',
sortorder: 'asc',
usepager: true,
title: 'Persons',
useRp: true,
rp: 5,
rpOptions: [5, 10, 15, 20, 25, 40],
showTableToggleBtn: true,
width: 900,
height: 'auto',
preProcess: function (data) {
var rp = getFgRowsPerPage($('#firstTable'));
for (i = data.rows.length; i < rp; ++i) {
data.rows.push({ 'id': '', 'cell': ['', '', '', '', '', ''] });
}
return data;
}
}); // flexigrid
setupGrid($('#firstTable'));
} //setupFirstTable
function add(com, grid) {
try {
closeFormByGrid(grid);
showAddFormByGrid(grid, formHtml);
} catch (e) {
alert('error ' + e);
}
}
function edit(com, grid) {
closeFormByGrid(grid);
var items = $('.trSelected', grid);
var item = items[0];
var pk = item.id.substr(3);
if (pk.length == 0) return;
$.ajax({
url: '/People/Management/GetUpdated/' + pk,
type: 'POST',
success: function (data) {
showEditForm(item, formHtml, function () {
var form = $('form', grid);
$('#PersonId', form).val(data.Record.PersonId);
$('#Username', form).val(data.Record.Username);
$('#Firstname', form).val(data.Record.Firstname);
$('#Lastname', form).val(data.Record.Lastname);
$('input[id=Country_CountryId]', form).val(data.Record.CountryId);
$('#FavoriteNumber', form).val(data.Record.FavoriteNumber);
$('#RowVersion', form).val(data.Record.RowVersion);
$('#Country_CountryId', form).ajc().showCaption();
setFgEditText(grid, 'Username', data.Record.Username);
setFgEditText(grid, 'Firstname', data.Record.Firstname);
setFgEditText(grid, 'Lastname', data.Record.Lastname);
setFgEditText(grid, 'FavoriteNumber', data.Record.FavoriteNumber);
}); //showEditForm
} //success
}); //ajax
}//edit
function del(com, grid) {
var deleteIt = confirm('Do you want to delete the selected record?');
if (!deleteIt) return;
var pk = getCurrentRowPk(grid);
var version = getFgGridColumnText(grid, 'RowVersion');
// alert(pk + " " + version + " " + encodeURIComponent(version));
$.ajax({
url: '/People/Management/Delete',
type: 'POST',
data: 'pk=' + pk + '&version=' + encodeURIComponent(version),
success: function (result) {
if (result.IsOk) {
$('#firstTable').flexReload();
}
}
});
}
</script>
Lastly we put this at the end of the html:
<script>
$(function () {
_canRender = true;
});
</script>
Here's the Flexigrid form-inliner helper functions:
function showEditForm(selectedTr, html, assignerFunc) {
$(selectedTr).after('<tr class="fgEdit" editId=' + selectedTr.id + '><td width="1" colspan="20" style="border-width: thin; border-top: thick; border-color: #EEE; white-space: normal;"><span></span></td></tr>');
var content = $('td > span', $(selectedTr).next());
// var form = $(content).hide().html(html);
var form = $(content).html(html);
assignerFunc();
$(content).show();
}
function showAddFormByGrid(grid, formHtml) {
var tbl = $('.bDiv table', grid);
showAddForm(tbl, formHtml);
}
function showAddForm(tbl, formHtml) {
var tbody = $('tbody', tbl);
if (tbody.length == 0) {
$(tbl).append($('<tbody/>'));
tbody = $('tbody', tbl);
}
$(tbody).prepend('<tr class="fgEdit"><td width="1" colspan="20" style="border-width: thin; border-top: thick; border-color: #EEE; white-space: normal"><span></span></td></tr>');
var content = $('tr td span', tbody);
$(content).html(formHtml);
}
Flexigrid CRUD(inline form) example with ASP.NET MVC, complete with model validation:
http://code.google.com/p/flexigrid-crud-example/downloads/list
Known issues:
1. Not with flexigrid per se; but when inside of flexigrid, the jQuery Ajax ComboBox's result area is far from its textbox when using Firefox or IE, have made a work-around in Chrome though. If you could lend a help in correcting that problem of jQuery Ajax ComboBox, I'll be more than glad to accept a patch.
2. If the flexigrid has no rows, the inline form is not in full width. Accepting patch for flexigrid code or helper code.
![]() |
| Sample output |
![]() |
| jQuery Ajax ComboBox's detached result screenshot(IE and Firefox problem) |
$.post returns incorrect empty string
You'll receive [object XMLDocument] in your jQuery $.post's data ...
..., when you return empty string from your controller:
In order to prevent that problem, just add "text" parameter to your $.post method return type
var $xhr2 = $.post(
options.init_src,
{
'lookupId': theValue,
'field': options.field,
'primary_key': options.primary_key,
'db_table': options.db_table
},
function (data) {
$input.val(data); // textbox receives [object XMLDocument]
}
);
..., when you return empty string from your controller:
public string Caption(string lookupId)
{
return "";
}
In order to prevent that problem, just add "text" parameter to your $.post method return type
var $xhr2 = $.post(
options.init_src,
{
'lookupId': theValue,
'field': options.field,
'primary_key': options.primary_key,
'db_table': options.db_table
},
function (data) {
$input.val(data); // receives "" string now.
},
"text"
);
Subscribe to:
Posts (Atom)

